diff options
204 files changed, 8102 insertions, 6201 deletions
@@ -9,5 +9,8 @@ # html files (as generated from markdown) *.html +# checksums file as used by $ScriptInstallUpdate +checksums.json + # Mac OS X folder settings file .DS_Store diff --git a/BRANCHES.md b/BRANCHES.md new file mode 100644 index 0000000..dc4f4ac --- /dev/null +++ b/BRANCHES.md @@ -0,0 +1,50 @@ +Installing from branches +======================== + +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 Binary files differnew file mode 100644 index 0000000..2fc3c9b --- /dev/null +++ b/CERTIFICATES.d/01-dialog-A.avif diff --git a/CERTIFICATES.d/02-dialog-B.avif b/CERTIFICATES.d/02-dialog-B.avif Binary files differnew file mode 100644 index 0000000..5e408ab --- /dev/null +++ b/CERTIFICATES.d/02-dialog-B.avif diff --git a/CERTIFICATES.d/03-window.avif b/CERTIFICATES.d/03-window.avif Binary files differnew file mode 100644 index 0000000..96039a3 --- /dev/null +++ b/CERTIFICATES.d/03-window.avif diff --git a/CERTIFICATES.d/04-certificate.avif b/CERTIFICATES.d/04-certificate.avif Binary files differnew file mode 100644 index 0000000..e666314 --- /dev/null +++ b/CERTIFICATES.d/04-certificate.avif diff --git a/CERTIFICATES.md b/CERTIFICATES.md new file mode 100644 index 0000000..69d6c18 --- /dev/null +++ b/CERTIFICATES.md @@ -0,0 +1,83 @@ +Certificate name from browser +============================= + +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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*". + + + +The dialog will change, click "*More information*". + + + +A new window opens, click the button "*View Certificate*". (That window +can be closed now.) + + + +A new tab opens, showing information on the server certificate and its +chain of trust. The leftmost certificate is what we are interested in. + + + +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 Gotify](doc/mod/notification-gotify.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 9024268..5bf5d08 100644 --- a/CONTRIBUTIONS.md +++ b/CONTRIBUTIONS.md @@ -1,6 +1,13 @@ Past Contributions ================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) + [⬅️ Go back to main README](README.md) Thanks a lot for your contributions! ❤️ @@ -13,29 +20,39 @@ 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) +* [Ilya Kulakov](mailto:kulakov.ilya@gmail.com) (@Kentzo) +* [Leonardo David Monteiro](mailto:leo@cub3.xyz) (@leosfsm) * [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) ## Donations Add yourself to the list, -[donate with PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)! +[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 diff --git a/DEBUG.md b/DEBUG.md new file mode 100644 index 0000000..d5e9beb --- /dev/null +++ b/DEBUG.md @@ -0,0 +1,63 @@ +Debug output and logs +===================== + +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 a53ae0f..79773bd 100644 --- a/INITIAL-COMMANDS.md +++ b/INITIAL-COMMANDS.md @@ -1,37 +1,63 @@ Initial commands ================ +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) + [⬅️ Go back to main README](README.md) -> ⚠️ **Warning**: These command are inteneded for initial setup. If you are +> ⚠️ **Warning**: These commands are intended 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/R3.pem" dst-path="letsencrypt-R3.pem" as-value; + :local BaseUrl "https://git.eworm.de/cgit/routeros-scripts/plain/"; + :local CertFileName "ISRG-Root-X2.pem"; + :local CertFingerprint "69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470"; + + :put "Importing certificate..."; + /tool/fetch ($BaseUrl . "certs/" . $CertFileName) dst-path=$CertFileName as-value; :delay 1s; - /certificate/import file-name=letsencrypt-R3.pem passphrase=""; - :if ([ :len [ /certificate/find where fingerprint="67add1166b020ae61b8f5fc96813c04c2aa589960796865572a3c7e737613dfd" or fingerprint="96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6" ] ] != 2) do={ + /certificate/import file-name=$CertFileName passphrase=""; + :if ([ :len [ /certificate/find where fingerprint=$CertFingerprint ] ] != 1) do={ :error "Something is wrong with your certificates!"; }; - /file/remove "letsencrypt-R3.pem"; :delay 1s; + :put "Renaming global-config-overlay, if exists..."; + /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/add name=$Script source=([ /tool/fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script . ".rsc") output=user as-value]->"data"); + :put "Installing $Script..."; + /system/script/remove [ find where name=$Script ]; + /system/script/add name=$Script owner=$Script source=([ /tool/fetch check-certificate=yes-without-crl ($BaseUrl . $Script . ".rsc") output=user as-value]->"data"); }; + :put "Loading configuration and functions..."; /system/script { run global-config; run global-functions; }; + :put "Scheduling to load configuration and 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; }"; + :put "Renaming certificate by its common-name..."; :global CertificateNameByCN; - $CertificateNameByCN "R3"; - $CertificateNameByCN "ISRG Root X1"; + $CertificateNameByCN $CertFingerprint; }; Then continue setup with [scheduled automatic updates](README.md#scheduled-automatic-updates) or [editing configuration](README.md#editing-configuration). +## 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. + --- [⬅️ Go back to main README](README.md) [⬆️ Go back to top](#top) @@ -4,33 +4,36 @@ CAPSMAN = $(wildcard *.capsman.rsc) LOCAL = $(wildcard *.local.rsc) -WIFIWAVE2 = $(wildcard *.wifiwave2.rsc) +WIFI = $(wildcard *.wifi.rsc) MARKDOWN = $(wildcard *.md doc/*.md doc/mod/*.md) HTML = $(MARKDOWN:.md=.html) -all: $(CAPSMAN) $(LOCAL) $(WIFIWAVE2) $(HTML) +all: $(CAPSMAN) $(LOCAL) $(WIFI) $(HTML) checksums.json %.html: %.md Makefile markdown $< | sed 's/href="\([-_\./[:alnum:]]*\)\.md"/href="\1.html"/g' > $@ -%.local.rsc: %.template.rsc Makefile - sed -e '/\/caps-man/d' -e '/\/interface\/wifiwave2/d' -e 's|%TEMPL%|.local|' \ - -e '/^# NOT \/interface\/wireless #$$/,/^# NOT \/interface\/wireless #$$/d' \ +%.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' \ -e '/^# !!/,/^# !!/c # !! Do not edit this file, it is generated from template!' \ < $< > $@ -%.capsman.rsc: %.template.rsc Makefile - sed -e '/\/interface\/wifiwave2/d' -e '/\/interface\/wireless/d' -e 's|%TEMPL%|.capsman|' \ - -e '/^# NOT \/caps-man #$$/,/^# NOT \/caps-man #$$/d' \ +%.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' \ -e '/^# !!/,/^# !!/c # !! Do not edit this file, it is generated from template!' \ < $< > $@ -%.wifiwave2.rsc: %.template.rsc Makefile - sed -e '/\/caps-man/d' -e '/\/interface\/wireless/d' -e 's|%TEMPL%|.wifiwave2|' \ - -e '/^# NOT \/interface\/wifiwave2 #$$/,/^# NOT \/interface\/wifiwave2 #$$/d' \ +%.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!' \ < $< > $@ +checksums.json: contrib/checksums.sh *.rsc */*.rsc + contrib/checksums.sh + clean: - rm -f $(HTML) + rm -f $(HTML) checksums.json diff --git a/README.d/01-download-certs.avif b/README.d/01-download-certs.avif Binary files differindex 4da73fa..d41ca05 100644 --- a/README.d/01-download-certs.avif +++ b/README.d/01-download-certs.avif diff --git a/README.d/02-import-certs.avif b/README.d/02-import-certs.avif Binary files differindex 308be5b..bf7d577 100644 --- a/README.d/02-import-certs.avif +++ b/README.d/02-import-certs.avif diff --git a/README.d/03-check-certs.avif b/README.d/03-check-certs.avif Binary files differindex adf1161..4717b3e 100644 --- a/README.d/03-check-certs.avif +++ b/README.d/03-check-certs.avif diff --git a/README.d/11-schedule-script.avif b/README.d/11-schedule-script.avif Binary files differindex 27541b7..d6eb0f8 100644 --- a/README.d/11-schedule-script.avif +++ b/README.d/11-schedule-script.avif diff --git a/README.d/12-setup-lease-script.avif b/README.d/12-setup-lease-script.avif Binary files differindex 365e0e8..fb4024e 100644 --- a/README.d/12-setup-lease-script.avif +++ b/README.d/12-setup-lease-script.avif diff --git a/README.d/upstream.png b/README.d/upstream.png Binary files differnew file mode 100644 index 0000000..fd5e877 --- /dev/null +++ b/README.d/upstream.png @@ -4,23 +4,26 @@ RouterOS Scripts [](https://github.com/eworm-de/routeros-scripts/stargazers) [](https://github.com/eworm-de/routeros-scripts/network) [](https://github.com/eworm-de/routeros-scripts/watchers) -[](https://mikrotik.com/download/changelogs/) +[](https://mikrotik.com/download/changelogs/) [](https://t.me/routeros_scripts) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)  -[RouterOS](https://mikrotik.com/software) is the operating system developed -by [MikroTik](https://mikrotik.com/aboutus) for networking tasks. This -repository holds a number of [scripts](https://wiki.mikrotik.com/wiki/Manual:Scripting) +[RouterOS ↗️](https://mikrotik.com/software) is the operating system developed +by [MikroTik ↗️](https://mikrotik.com/aboutus) for networking tasks. This +repository holds a number of [scripts ↗️](https://wiki.mikrotik.com/wiki/Manual:Scripting) to manage RouterOS devices or extend their functionality. *Use at your own risk*, pay attention to -[license and warranty](#license-and-warranty)! +[license and warranty](#license-and-warranty), and +[disclaimer on external links](#disclaimer-on-external-links)! 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 @@ -32,6 +35,20 @@ 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 ------------- @@ -45,9 +62,9 @@ First time users should take the long way below. ### Live presentation Want to see it in action? I've had a presentation [Repository based -RouterOS script distribution](https://www.youtube.com/watch?v=B9neG3oAhcY) +RouterOS script distribution ↗️](https://www.youtube.com/watch?v=B9neG3oAhcY) including demonstation recorded live at [MUM Europe -2019](https://mum.mikrotik.com/2019/EU/) in Vienna. +2019 ↗️](https://mum.mikrotik.com/2019/EU/) in Vienna. > ⚠️ **Warning**: Some details changed. So see the presentation, then follow > the steps below for up-to-date commands. @@ -59,29 +76,31 @@ 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/R3.pem" dst-path="letsencrypt-R3.pem"; + /tool/fetch "https://git.eworm.de/cgit/routeros-scripts/plain/certs/ISRG-Root-X2.pem" dst-path="isrg-root-x2.pem";  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 -files to your MikroTik device. +file to your MikroTik device. + +* [ISRG Root X2 ↗️](https://letsencrypt.org/certs/isrg-root-x2.pem) -* [ISRG Root X1](https://letsencrypt.org/certs/isrgrootx1.pem) -* Let's Encrypt [R3](https://letsencrypt.org/certs/lets-encrypt-r3.pem) +Then we import the certificate. -Then we import the certificates. + /certificate/import file-name="isrg-root-x2.pem" passphrase=""; - /certificate/import file-name=letsencrypt-R3.pem passphrase=""; +Do not worry that the command is not shown - that happens because it contains +a sensitive property, the passphrase.  -For basic verification we rename the certificates and print their count. Make -sure the certificate count is **two**. +For basic verification we rename the certificate and print it by +fingerprint. Make sure exactly this one certificate ("*ISRG-Root-X2*") +is shown. - /certificate/set name="R3" [ find where fingerprint="67add1166b020ae61b8f5fc96813c04c2aa589960796865572a3c7e737613dfd" ]; - /certificate/set name="ISRG-Root-X1" [ find where fingerprint="96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6" ]; - /certificate/print count-only where fingerprint="67add1166b020ae61b8f5fc96813c04c2aa589960796865572a3c7e737613dfd" or fingerprint="96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6"; + /certificate/set name="ISRG-Root-X2" [ find where common-name="ISRG Root X2" ]; + /certificate/print proplist=name,fingerprint where fingerprint="69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470";  @@ -93,7 +112,7 @@ date and time is set correctly! Now let's download the main scripts and add them in configuration on the fly. - :foreach Script in={ "global-config"; "global-config-overlay"; "global-functions" } do={ /system/script/add name=$Script source=([ /tool/fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script . ".rsc") output=user as-value]->"data"); }; + :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"); };  @@ -104,6 +123,9 @@ And finally load configuration and functions and add the scheduler.  +> 💡️ **Hint**: You see complaints regarding syntax errors? Most likely the +> RouterOS on your device is too old. Check for updates! + ### Scheduled automatic updates The last step is optional: Add this scheduler **only** if you want the @@ -125,6 +147,11 @@ Save changes and exit with `Ctrl-o`.  +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. + To apply your changes run `global-config`, which will automatically load the overlay as well: @@ -137,7 +164,7 @@ This last step is required when ever you make changes to your configuration. > ℹ️ **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=[ $Unix2Dos [ get global-config-overlay source ] ] global-config-overlay;` +> `/system/script/set source=[ :tocrlf [ get global-config-overlay source ] ] global-config-overlay;` Updating scripts ---------------- @@ -168,11 +195,11 @@ 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 every hour to make sure not to +[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 miss an update. - /system/scheduler/add name="check-routeros-update" interval=1h on-event="/system/script/run check-routeros-update;"; + /system/scheduler/add name="check-routeros-update" interval=1d start-time=startup on-event="/system/script/run check-routeros-update;";  @@ -191,59 +218,62 @@ There's much more to explore... Have fun! Available scripts ----------------- -* [Find and remove access list duplicates](doc/accesslist-duplicates.md) -* [Upload backup to Mikrotik cloud](doc/backup-cloud.md) -* [Send backup via e-mail](doc/backup-email.md) -* [Save configuration to fallback partition](doc/backup-partition.md) -* [Upload backup to server](doc/backup-upload.md) -* [Download packages for CAP upgrade from CAPsMAN](doc/capsman-download-packages.md) -* [Run rolling CAP upgrades from CAPsMAN](doc/capsman-rolling-upgrade.md) -* [Renew locally issued certificates](doc/certificate-renew-issued.md) -* [Renew certificates and notify on expiration](doc/check-certificates.md) -* [Notify about health state](doc/check-health.md) -* [Notify on LTE firmware upgrade](doc/check-lte-firmware-upgrade.md) -* [Notify on RouterOS update](doc/check-routeros-update.md) -* [Collect MAC addresses in wireless access list](doc/collect-wireless-mac.md) -* [Use wireless network with daily psk](doc/daily-psk.md) -* [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 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) -* [Run other scripts on DHCP lease](doc/lease-script.md) -* [Manage LEDs dark mode](doc/leds-mode.md) -* [Forward log messages via notification](doc/log-forward.md) -* [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) -* [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) -* [Act on received SMS](doc/sms-action.md) -* [Forward received SMS](doc/sms-forward.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) +* [Find and remove access list duplicates](doc/accesslist-duplicates.md) (`accesslist-duplicates`) +* [Upload backup to Mikrotik cloud](doc/backup-cloud.md) (`backup-cloud`) +* [Send backup via e-mail](doc/backup-email.md) (`backup-email`) +* [Save configuration to fallback partition](doc/backup-partition.md) (`backup-partition`) +* [Upload backup to server](doc/backup-upload.md) (`backup-upload`) +* [Download packages for CAP upgrade from CAPsMAN](doc/capsman-download-packages.md) (`capsman-download-packages`) +* [Run rolling CAP upgrades from CAPsMAN](doc/capsman-rolling-upgrade.md) (`capsman-rolling-upgrade`) +* [Renew locally issued certificates](doc/certificate-renew-issued.md) (`certificate-renew-issued`) +* [Renew certificates and notify on expiration](doc/check-certificates.md) (`check-certificates`) +* [Notify about health state](doc/check-health.md) (`check-health`) +* [Notify on LTE firmware upgrade](doc/check-lte-firmware-upgrade.md) (`check-lte-firmware-upgrade`) +* [Check perpetual license on CHR](doc/check-perpetual-license.md) (`check-perpetual-license`) +* [Notify on RouterOS update](doc/check-routeros-update.md) (`check-routeros-update`) +* [Collect MAC addresses in wireless access list](doc/collect-wireless-mac.md) (`collect-wireless-mac`) +* [Use wireless network with daily psk](doc/daily-psk.md) (`daily-psk`) +* [Comment DHCP leases with info from access list](doc/dhcp-lease-comment.md) (`dhcp-lease-comment`) +* [Create DNS records for DHCP leases](doc/dhcp-to-dns.md) (`dhcp-to-dns`) +* [Automatically upgrade firmware and reboot](doc/firmware-upgrade-reboot.md) (`firmware-upgrade-reboot`) +* [Download, import and update firewall address-lists](doc/fw-addr-lists.md) (`fw-addr-lists`) +* [Wait for global functions und modules](doc/global-wait.md) (`global-wait`) +* [Send GPS position to server](doc/gps-track.md) (`gps-track`) +* [Use WPA network with hotspot credentials](doc/hotspot-to-wpa.md) (`hotspot-to-wpa` & `hotspot-to-wpa-cleanup`) +* [Create DNS records for IPSec peers](doc/ipsec-to-dns.md) (`ipsec-to-dns`) +* [Update configuration on IPv6 prefix change](doc/ipv6-update.md) (`ipv6-update`) +* [Manage IP addresses with bridge status](doc/ip-addr-bridge.md) (`ip-addr-bridge`) +* [Run other scripts on DHCP lease](doc/lease-script.md) (`lease-script`) +* [Manage LEDs dark mode](doc/leds-mode.md) (`leds-day-mode`, `leds-night-mode` & `leds-toggle-mode`) +* [Forward log messages via notification](doc/log-forward.md) (`log-forward`) +* [Mode button with multiple presses](doc/mode-button.md) (`mode-button`) +* [Manage DNS and DoH servers from netwatch](doc/netwatch-dns.md) (`netwatch-dns`) +* [Notify on host up and down](doc/netwatch-notify.md) (`netwatch-notify`) +* [Visualize OSPF state via LEDs](doc/ospf-to-leds.md) (`ospf-to-leds`) +* [Manage system update](doc/packages-update.md) (`packages-update`) +* [Run scripts on ppp connection](doc/ppp-on-up.md) (`ppp-on-up`) +* [Act on received SMS](doc/sms-action.md) (`sms-action`) +* [Forward received SMS](doc/sms-forward.md) (`sms-forward`) +* [Play Super Mario theme](doc/super-mario-theme.md) (`super-mario-theme`) +* [Chat with your router and send commands via Telegram bot](doc/telegram-chat.md) (`telegram-chat`) +* [Install LTE firmware upgrade](doc/unattended-lte-firmware-upgrade.md) (`unattended-lte-firmware-upgrade`) +* [Update GRE configuration with dynamic addresses](doc/update-gre-address.md) (`update-gre-address`) +* [Update tunnelbroker configuration](doc/update-tunnelbroker.md) (`update-tunnelbroker`) Available modules ----------------- -* [Manage ports in bridge](doc/mod/bridge-port-to.md) -* [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 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) +* [Manage ports in bridge](doc/mod/bridge-port-to.md) (`mod/bridge-port-to`) +* [Manage VLANs on bridge ports](doc/mod/bridge-port-vlan.md) (`mod/bridge-port-vlan`) +* [Inspect variables](doc/mod/inspectvar.md) (`mod/inspectvar`) +* [IP address calculation](doc/mod/ipcalc.md) (`mod/ipcalc`) +* [Send notifications via e-mail](doc/mod/notification-email.md) (`mod/notification-email`) +* [Send notifications via Gotify](doc/mod/notification-gotify.md) (`mod/notification-gotify`) +* [Send notifications via Matrix](doc/mod/notification-matrix.md) (`mod/notification-matrix`) +* [Send notifications via Ntfy](doc/mod/notification-ntfy.md) (`mod/notification-ntfy`) +* [Send notifications via Telegram](doc/mod/notification-telegram.md) (`mod/notification-telegram`) +* [Download script and run it once](doc/mod/scriptrunonce.md) (`mod/scriptrunonce`) +* [Import ssh keys for public key authentication](doc/mod/ssh-keys-import.md) (`mod/ssh-keys-import`) Installing custom scripts & modules ----------------------------------- @@ -300,7 +330,7 @@ Possibly a scheduler and other configuration has to be removed as well. Contact ------- -We have a Telegram Group [RouterOS-Scripts](https://t.me/routeros_scripts)! +We have a Telegram Group [RouterOS-Scripts ↗️](https://t.me/routeros_scripts)! [](https://t.me/routeros_scripts) @@ -324,7 +354,7 @@ at github. 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 to -[donate with PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J). +[donate with PayPal ↗️](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J). [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) @@ -343,9 +373,26 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the [GNU General Public License](COPYING.md) for more details. +Disclaimer on external links +---------------------------- + +Our website contains links to the websites of third parties ("external +links"). As the content of these websites is not under our control, we +cannot assume any liability for such external content. In all cases, the +provider of information of the linked websites is liable for the content +and accuracy of the information provided. At the point in time when the +links were placed, no infringements of the law were recognisable to us. +As soon as an infringement of the law becomes known to us, we will +immediately remove the link in question. + +> 💡️ **Hint**: All external links are marked with an arrow pointing +> diagonally in an up-right (or north-east) direction (↗️). + Upstream -------- +[](https://rsc.eworm.de/) + URL: [GitHub.com](https://github.com/eworm-de/routeros-scripts#routeros-scripts) diff --git a/accesslist-duplicates.capsman.rsc b/accesslist-duplicates.capsman.rsc index 8831cc6..27546c8 100644 --- a/accesslist-duplicates.capsman.rsc +++ b/accesslist-duplicates.capsman.rsc @@ -1,42 +1,37 @@ #!rsc by RouterOS # RouterOS script: accesslist-duplicates.capsman -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # print duplicate antries in wireless access list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/accesslist-duplicates.md +# https://rsc.eworm.de/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 ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:local Seen ({}); -:local Shown ({}); + :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 ]; - :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); + :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!" ] ]; - :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; - } + :if ([ :typeof $Remove ] = "num") do={ + :put ("Removing numeric id " . $Remove . "...\n"); + /caps-man/access-list/remove $Remove; } } + :set ($Seen->$Mac) 1; } - :set Seen ($Seen, $Mac); +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/accesslist-duplicates.local.rsc b/accesslist-duplicates.local.rsc index d4b8867..589815d 100644 --- a/accesslist-duplicates.local.rsc +++ b/accesslist-duplicates.local.rsc @@ -1,42 +1,37 @@ #!rsc by RouterOS # RouterOS script: accesslist-duplicates.local -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # print duplicate antries in wireless access list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/accesslist-duplicates.md +# https://rsc.eworm.de/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 ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:local Seen ({}); -:local Shown ({}); + :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 ]; - :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); + :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!" ] ]; - :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; - } + :if ([ :typeof $Remove ] = "num") do={ + :put ("Removing numeric id " . $Remove . "...\n"); + /interface/wireless/access-list/remove $Remove; } } + :set ($Seen->$Mac) 1; } - :set Seen ($Seen, $Mac); +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/accesslist-duplicates.template.rsc b/accesslist-duplicates.template.rsc index 85d444e..ccbca3d 100644 --- a/accesslist-duplicates.template.rsc +++ b/accesslist-duplicates.template.rsc @@ -1,51 +1,46 @@ #!rsc by RouterOS # RouterOS script: accesslist-duplicates%TEMPL% -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # print duplicate antries in wireless access list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/accesslist-duplicates.md +# 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. -:local 0 "accesslist-duplicates%TEMPL%"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global Read; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:local Seen ({}); -:local Shown ({}); + :local Seen ({}); -:foreach AccList in=[ /caps-man/access-list/find where mac-address!="00:00:00:00:00:00" ] do={ -:foreach AccList in=[ /interface/wifiwave2/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/wifiwave2/access-list/get $AccList mac-address ]; - :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={ - /caps-man/access-list/print where mac-address=$Mac; - /interface/wifiwave2/access-list/print where mac-address=$Mac; - /interface/wireless/access-list/print where mac-address=$Mac; - :set Shown ($Shown, $Mac); + :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!" ] ]; - :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; - /interface/wifiwave2/access-list/remove $Remove; - /interface/wireless/access-list/remove $Remove; - } + :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; } - :set Seen ($Seen, $Mac); +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/accesslist-duplicates.wifi.rsc b/accesslist-duplicates.wifi.rsc new file mode 100644 index 0000000..527ebb4 --- /dev/null +++ b/accesslist-duplicates.wifi.rsc @@ -0,0 +1,37 @@ +#!rsc by RouterOS +# RouterOS script: accesslist-duplicates.wifi +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> +# 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/accesslist-duplicates.wifiwave2.rsc b/accesslist-duplicates.wifiwave2.rsc deleted file mode 100644 index 7c0ecf4..0000000 --- a/accesslist-duplicates.wifiwave2.rsc +++ /dev/null @@ -1,42 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: accesslist-duplicates.wifiwave2 -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md -# -# print duplicate antries in wireless access list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/accesslist-duplicates.md -# -# !! Do not edit this file, it is generated from template! - -:local 0 "accesslist-duplicates.wifiwave2"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global Read; - -:local Seen ({}); -:local Shown ({}); - -:foreach AccList in=[ /interface/wifiwave2/access-list/find where mac-address!="00:00:00:00:00:00" ] do={ - :local Mac [ /interface/wifiwave2/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/wifiwave2/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/wifiwave2/access-list/remove $Remove; - } - } - } - } - :set Seen ($Seen, $Mac); -} diff --git a/backup-cloud.rsc b/backup-cloud.rsc index 934edb9..c4e23b2 100644 --- a/backup-cloud.rsc +++ b/backup-cloud.rsc @@ -1,61 +1,104 @@ #!rsc by RouterOS # RouterOS script: backup-cloud -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md # # provides: backup-script, order=40 +# requires RouterOS, version=7.15 # # upload backup to MikroTik cloud -# https://git.eworm.de/cgit/routeros-scripts/about/doc/backup-cloud.md +# https://rsc.eworm.de/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 FormatLine; -:global LogPrintExit2; -:global RandomDelay; -:global ScriptFromTerminal; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; -:global WaitFullyConnected; - -$ScriptLock $0; -$WaitFullyConnected; - -:if ([ $ScriptFromTerminal $0 ] = false && $BackupRandomDelay > 0) do={ - $RandomDelay $BackupRandomDelay; -} - +:local ExitOK false; :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 ]; + :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={ - /system/backup/cloud/upload-file action=create-and-upload \ - password=$BackupPassword; + $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; } - :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" . \ - [ $FormatLine "Name" ($Cloud->"name") ] . "\n" . \ - [ $FormatLine "Size" ($Cloud->"size" . " B (" . ($Cloud->"size" / 1024) . " KiB)") ] . "\n" . \ - [ $FormatLine "Download key" ($Cloud->"secret-download-key") ]); silent=true }); + $RmDir "tmpfs/backup-cloud"; } on-error={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "floppy-disk,warning-sign" ] . "Cloud backup failed"); \ - message=("Failed uploading backup for " . $Identity . " to cloud!\n\n" . [ $DeviceInfo ]) }); - $LogPrintExit2 error $0 ("Failed uploading backup for " . $Identity . " to cloud!") true; + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/backup-email.rsc b/backup-email.rsc index 95e9015..d097301 100644 --- a/backup-email.rsc +++ b/backup-email.rsc @@ -1,111 +1,140 @@ #!rsc by RouterOS # RouterOS script: backup-email -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md # # provides: backup-script, order=20 +# requires RouterOS, version=7.15 # # create and email backup and config file -# https://git.eworm.de/cgit/routeros-scripts/about/doc/backup-email.md +# https://rsc.eworm.de/doc/backup-email.md -:local 0 "backup-email"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global BackupPassword; -:global BackupRandomDelay; -:global BackupSendBinary; -:global BackupSendExport; -:global BackupSendGlobalConfig; -:global Domain; -:global Identity; - -:global CharacterReplace; -:global DeviceInfo; -:global FormatLine; -:global LogPrintExit2; -:global MkDir; -:global RandomDelay; -:global ScriptFromTerminal; -:global ScriptLock; -:global SendEMail2; -:global SymbolForNotification; -:global WaitForFile; -:global WaitFullyConnected; - -:if ([ :typeof $SendEMail2 ] = "nothing") do={ - $LogPrintExit2 error $0 ("The module for sending notifications via e-mail is not installed.") true; -} +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:if ($BackupSendBinary != true && \ - $BackupSendExport != true) do={ - $LogPrintExit2 error $0 ("Configured to send neither backup nor config export.") true; -} + :global BackupPassword; + :global BackupRandomDelay; + :global BackupSendBinary; + :global BackupSendExport; + :global BackupSendGlobalConfig; + :global Domain; + :global Identity; + :global PackagesUpdateBackupFailure; -$ScriptLock $0; -$WaitFullyConnected; + :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 ([ $ScriptFromTerminal $0 ] = false && $BackupRandomDelay > 0) do={ - $RandomDelay $BackupRandomDelay; -} + :if ([ :typeof $SendEMail2 ] = "nothing") do={ + $LogPrint error $ScriptName ("The module for sending notifications via e-mail is not installed."); + :set ExitOK true; + :error false; + } -# filename based on identity -:local DirName ("tmpfs/" . $0); -:local FileName [ $CharacterReplace ($Identity . "." . $Domain) "." "_" ]; -:local FilePath ($DirName . "/" . $FileName); -:local BackupFile "none"; -:local ExportFile "none"; -:local ConfigFile "none"; -:local Attach ({}); - -:if ([ $MkDir $DirName ] = false) do={ - $LogPrintExit2 error $0 ("Failed creating directory!") true; -} + :if ($BackupSendBinary != true && \ + $BackupSendExport != true) do={ + $LogPrint error $ScriptName ("Configured to send neither backup nor config export."); + :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")); -} + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set PackagesUpdateBackupFailure true; + :set ExitOK true; + :error false; + } -# 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")); -} + :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; + } -# 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"); - $WaitForFile ($FilePath . ".conf.txt"); - :set ConfigFile ($FileName . ".conf.txt"); - :set Attach ($Attach, ($FilePath . ".conf.txt")); -} + $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 }); -# 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" . \ - [ $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={ - $LogPrintExit2 warning $0 ("Files are still available, sending e-mail failed.") 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); } - :delay 1s; - :set I ($I + 1); +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/backup-partition.rsc b/backup-partition.rsc index b03ad7e..1f0cf2e 100644 --- a/backup-partition.rsc +++ b/backup-partition.rsc @@ -1,39 +1,126 @@ #!rsc by RouterOS # RouterOS script: backup-partition -# Copyright (c) 2022-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2022-2025 Christian Hesse <mail@eworm.de> +# 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://git.eworm.de/cgit/routeros-scripts/about/doc/backup-partition.md +# https://rsc.eworm.de/doc/backup-partition.md -:local 0 "backup-partition"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global LogPrintExit2; -:global ScriptLock; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$ScriptLock $0; + :global BackupPartitionCopyBeforeFeatureUpdate; + :global PackagesUpdateBackupFailure; -:if ([ :len [ /partitions/find ] ] < 2) do={ - $LogPrintExit2 error $0 ("Device does not have a fallback partition.") true; -} + :global LogPrint; + :global ScriptFromTerminal; + :global ScriptLock; + :global VersionToNum; -:local ActiveRunning [ /partitions/find where active running ]; + :local CopyTo do={ + :local ScriptName [ :tostr $1 ]; + :local FallbackTo [ :toid $2 ]; + :local FallbackToName [ :tostr $3 ]; -:if ([ :len $ActiveRunning ] < 1) do={ - $LogPrintExit2 error $0 ("Device is not running from active partition.") true; -} + :global LogPrint; -:local ActiveRunningVar [ /partitions/get $ActiveRunning ]; + :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; + } + } -:do { - /partitions/save-config-to ($ActiveRunningVar->"fallback-to"); - $LogPrintExit2 info $0 ("Saved configuration to partition '" . \ - ($ActiveRunningVar->"fallback-to") . "'.") 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={ - $LogPrintExit2 error $0 ("Failed saving configuration to partition '" . \ - ($ActiveRunningVar->"fallback-to") . "'!") true; + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/backup-upload.rsc b/backup-upload.rsc index 4c8af4a..14c3914 100644 --- a/backup-upload.rsc +++ b/backup-upload.rsc @@ -1,133 +1,178 @@ #!rsc by RouterOS # RouterOS script: backup-upload -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# 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://git.eworm.de/cgit/routeros-scripts/about/doc/backup-upload.md +# https://rsc.eworm.de/doc/backup-upload.md -:local 0 "backup-upload"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global BackupPassword; -:global BackupRandomDelay; -:global BackupSendBinary; -:global BackupSendExport; -:global BackupSendGlobalConfig; -:global BackupUploadPass; -:global BackupUploadUrl; -:global BackupUploadUser; -:global Domain; -:global Identity; - -:global CharacterReplace; -:global DeviceInfo; -:global FormatLine; -:global IfThenElse; -:global LogPrintExit2; -:global MkDir; -:global RandomDelay; -:global ScriptFromTerminal; -:global ScriptLock; -: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; -} +: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; + } -$ScriptLock $0; -$WaitFullyConnected; + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set PackagesUpdateBackupFailure true; + :set ExitOK true; + :error false; + } -:if ([ $ScriptFromTerminal $0 ] = false && $BackupRandomDelay > 0) do={ - $RandomDelay $BackupRandomDelay; -} + :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; + } -# filename based on identity -:local DirName ("tmpfs/" . $0); -:local FileName [ $CharacterReplace ($Identity . "." . $Domain) "." "_" ]; -:local FilePath ($DirName . "/" . $FileName); -:local BackupFile "none"; -:local ExportFile "none"; -:local ConfigFile "none"; -:local Failed 0; - -:if ([ $MkDir $DirName ] = false) do={ - $LogPrintExit2 error $0 ("Failed creating directory!") true; -} + $WaitFullyConnected; -# 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; + :if ([ $ScriptFromTerminal $ScriptName ] = false && $BackupRandomDelay > 0) do={ + $RandomDelay $BackupRandomDelay; } - /file/remove ($FilePath . ".backup"); -} + # 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; + } -# create configuration export -:if ($BackupSendExport = true) do={ - /export terse show-sensitive file=$FilePath; - $WaitForFile ($FilePath . ".rsc"); - - :do { - /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".rsc") \ - user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".rsc"); - :set ExportFile ($FileName . ".rsc"); - } on-error={ - $LogPrintExit2 error $0 ("Uploading configuration export failed!") false; - :set ExportFile "failed"; - :set Failed 1; + # 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"); } - /file/remove ($FilePath . ".rsc"); -} + # 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"); - $WaitForFile ($FilePath . ".conf.txt"); - - :do { - /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".conf") \ - user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".conf.txt"); - :set ConfigFile ($FileName . ".conf"); - } on-error={ - $LogPrintExit2 error $0 ("Uploading global-config-overlay failed!") false; - :set ConfigFile "failed"; - :set Failed 1; + # 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"); } - /file/remove ($FilePath . ".conf.txt"); -} + :local FileInfo do={ + :local Name $1; + :local File $2; + + :global FormatLine; + :global HumanReadableNum; + :global IfThenElse; -$SendNotification2 ({ origin=$0; \ - subject=[ $IfThenElse ($Failed > 0) \ - ([ $SymbolForNotification "floppy-disk,warning-sign" ] . "Backup & Config upload with failure") \ - ([ $SymbolForNotification "floppy-disk,up-arrow" ] . "Backup & Config upload") ]; \ - message=("Backup and config export upload for " . $Identity . ".\n\n" . \ - [ $DeviceInfo ] . "\n\n" . \ - [ $FormatLine "Backup file" $BackupFile ] . "\n" . \ - [ $FormatLine "Export file" $ExportFile ] . "\n" . \ - [ $FormatLine "Config file" $ConfigFile ]); silent=true }); - -:if ($Failed = 1) do={ - :error "An error occured!"; + :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.capsman.rsc b/capsman-download-packages.capsman.rsc index 99ff052..25c43f5 100644 --- a/capsman-download-packages.capsman.rsc +++ b/capsman-download-packages.capsman.rsc @@ -1,89 +1,92 @@ #!rsc by RouterOS # RouterOS script: capsman-download-packages.capsman -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> # Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # download and cleanup packages for CAP installation from CAPsMAN -# https://git.eworm.de/cgit/routeros-scripts/about/doc/capsman-download-packages.md +# https://rsc.eworm.de/doc/capsman-download-packages.md # # !! Do not edit this file, it is generated from template! -:local 0 "capsman-download-packages.capsman"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global CleanFilePath; -:global DownloadPackage; -:global LogPrintExit2; -:global MkDir; -:global ScriptLock; -:global WaitFullyConnected; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$ScriptLock $0; -$WaitFullyConnected; + :global CleanFilePath; + :global DownloadPackage; + :global LogPrint; + :global MkDir; + :global RmFile; + :global ScriptLock; + :global WaitFullyConnected; -:local PackagePath [ $CleanFilePath [ /caps-man/manager/get package-path ] ]; -:local InstalledVersion [ /system/package/update/get installed-version ]; -:local Updated false; + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } + $WaitFullyConnected; -:if ([ :len $PackagePath ] = 0) do={ - $LogPrintExit2 warning $0 ("The CAPsMAN package path is not defined, can not download packages.") true; -} + :local PackagePath [ $CleanFilePath [ /caps-man/manager/get package-path ] ]; + :local InstalledVersion [ /system/package/update/get installed-version ]; + :local Updated false; -: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; + :if ([ :len $PackagePath ] = 0) do={ + $LogPrint warning $ScriptName ("The CAPsMAN package path is not defined, can not download packages."); + :set ExitOK true; + :error false; } - $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 ([ :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!"); } - :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 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"); + } } -} -: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 ([ :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 Script ([ /system/script/find where source~"\n# provides: capsman-rolling-upgrade\n" ]->0); - :if ([ :len $Script ] > 0) do={ - /system/script/run $Script; - } else={ - /caps-man/remote-cap/upgrade [ find where version!=$InstalledVersion ]; + :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 index 71bced0..b269838 100644 --- a/capsman-download-packages.template.rsc +++ b/capsman-download-packages.template.rsc @@ -1,106 +1,103 @@ #!rsc by RouterOS # RouterOS script: capsman-download-packages%TEMPL% -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> # Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # download and cleanup packages for CAP installation from CAPsMAN -# https://git.eworm.de/cgit/routeros-scripts/about/doc/capsman-download-packages.md +# https://rsc.eworm.de/doc/capsman-download-packages.md # -# !! This is just a template! Replace '%PATH%' with 'caps-man', -# !! 'interface/wireless' or 'interface/wifiwave2'! +# !! This is just a template to generate the real script! +# !! Pattern '%TEMPL%' is replaced, paths are filtered. -:local 0 "capsman-download-packages%TEMPL%"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global CleanFilePath; -:global DownloadPackage; -:global LogPrintExit2; -:global MkDir; -:global ScriptLock; -:global WaitFullyConnected; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$ScriptLock $0; -$WaitFullyConnected; + :global CleanFilePath; + :global DownloadPackage; + :global LogPrint; + :global MkDir; + :global RmFile; + :global ScriptLock; + :global WaitFullyConnected; -:local PackagePath [ $CleanFilePath [ /caps-man/manager/get package-path ] ]; -:local PackagePath [ $CleanFilePath [ /interface/wifiwave2/capsman/get package-path ] ]; -:local InstalledVersion [ /system/package/update/get installed-version ]; -:local Updated false; + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } + $WaitFullyConnected; -:if ([ :len $PackagePath ] = 0) do={ - $LogPrintExit2 warning $0 ("The CAPsMAN package path is not defined, can not download packages.") true; -} + :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 [ /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; + :if ([ :len $PackagePath ] = 0) do={ + $LogPrint warning $ScriptName ("The CAPsMAN package path is not defined, can not download packages."); + :set ExitOK true; + :error false; } - $LogPrintExit2 info $0 ("Created directory at CAPsMAN package path (" . $PackagePath . \ - "). Please place your packages!") false; -} -:foreach Package in=[ /file/find where type=package \ - package-version!=$InstalledVersion name~("^" . $PackagePath) ] do={ - :local File [ /file/get $Package ]; - :if ($File->"package-architecture" = "mips") do={ - :set ($File->"package-architecture") "mipsbe"; - } - :if ([ $DownloadPackage ($File->"package-name") $InstalledVersion \ - ($File->"package-architecture") $PackagePath ] = true) do={ - :set Updated true; - /file/remove $Package; + :if ([ :len [ /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!"); } -} -# NOT /interface/wifiwave2 # -: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 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"); + } } -} -: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; - } -} -# NOT /interface/wifiwave2 # -# NOT /caps-man # -:if ([ :len [ /file/find where type=package name~("^" . $PackagePath) ] ] = 0) do={ - $LogPrintExit2 info $0 ("No packages available, downloading default set.") false; - :foreach Arch in={ "arm"; "arm64" } do={ - :foreach Package in={ "routeros"; "wifiwave2" } do={ - :if ([ $DownloadPackage $Package $InstalledVersion $Arch $PackagePath ] = true) do={ - :set Updated true; + :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; + } } } } -} -# NOT /caps-man # -:if ($Updated = true) do={ - :local Script ([ /system/script/find where source~"\n# provides: capsman-rolling-upgrade\n" ]->0); - :if ([ :len $Script ] > 0) do={ - /system/script/run $Script; - } else={ - /caps-man/remote-cap/upgrade [ find where version!=$InstalledVersion ]; - /interface/wifiwave2/capsman/remote-cap/upgrade [ find where version!=$InstalledVersion ]; + :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 new file mode 100644 index 0000000..901bb0a --- /dev/null +++ b/capsman-download-packages.wifi.rsc @@ -0,0 +1,94 @@ +#!rsc by RouterOS +# RouterOS script: capsman-download-packages.wifi +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> +# Michael Gisbers <michael@gisbers.de> +# 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-download-packages.wifiwave2.rsc b/capsman-download-packages.wifiwave2.rsc deleted file mode 100644 index 6a12b2d..0000000 --- a/capsman-download-packages.wifiwave2.rsc +++ /dev/null @@ -1,74 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: capsman-download-packages.wifiwave2 -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md -# -# download and cleanup packages for CAP installation from CAPsMAN -# https://git.eworm.de/cgit/routeros-scripts/about/doc/capsman-download-packages.md -# -# !! Do not edit this file, it is generated from template! - -:local 0 "capsman-download-packages.wifiwave2"; -: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 [ /interface/wifiwave2/capsman/get package-path ] ]; -:local InstalledVersion [ /system/package/update/get installed-version ]; -:local Updated false; - -:if ([ :len $PackagePath ] = 0) do={ - $LogPrintExit2 warning $0 ("The CAPsMAN package path is not defined, can not download packages.") true; -} - -:if ([ :len [ /file/find where name=$PackagePath type="directory" ] ] = 0) do={ - :if ([ $MkDir $PackagePath ] = false) do={ - $LogPrintExit2 warning $0 ("Creating directory at CAPsMAN package path (" . \ - $PackagePath . ") failed!") true; - } - $LogPrintExit2 info $0 ("Created directory at CAPsMAN package path (" . $PackagePath . \ - "). Please place your packages!") false; -} - -:foreach Package in=[ /file/find where type=package \ - package-version!=$InstalledVersion name~("^" . $PackagePath) ] do={ - :local File [ /file/get $Package ]; - :if ($File->"package-architecture" = "mips") do={ - :set ($File->"package-architecture") "mipsbe"; - } - :if ([ $DownloadPackage ($File->"package-name") $InstalledVersion \ - ($File->"package-architecture") $PackagePath ] = true) do={ - :set Updated true; - /file/remove $Package; - } -} - -:if ([ :len [ /file/find where type=package name~("^" . $PackagePath) ] ] = 0) do={ - $LogPrintExit2 info $0 ("No packages available, downloading default set.") false; - :foreach Arch in={ "arm"; "arm64" } do={ - :foreach Package in={ "routeros"; "wifiwave2" } do={ - :if ([ $DownloadPackage $Package $InstalledVersion $Arch $PackagePath ] = true) do={ - :set Updated true; - } - } - } -} - -:if ($Updated = true) do={ - :local Script ([ /system/script/find where source~"\n# provides: capsman-rolling-upgrade\n" ]->0); - :if ([ :len $Script ] > 0) do={ - /system/script/run $Script; - } else={ - /interface/wifiwave2/capsman/remote-cap/upgrade [ find where version!=$InstalledVersion ]; - } -} diff --git a/capsman-rolling-upgrade.capsman.rsc b/capsman-rolling-upgrade.capsman.rsc index 7f83b6c..791b3db 100644 --- a/capsman-rolling-upgrade.capsman.rsc +++ b/capsman-rolling-upgrade.capsman.rsc @@ -1,40 +1,50 @@ #!rsc by RouterOS # RouterOS script: capsman-rolling-upgrade.capsman -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> # Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# https://rsc.eworm.de/COPYING.md # -# provides: capsman-rolling-upgrade +# provides: capsman-rolling-upgrade.capsman +# requires RouterOS, version=7.15 # # upgrade CAPs one after another -# https://git.eworm.de/cgit/routeros-scripts/about/doc/capsman-rolling-upgrade.md +# https://rsc.eworm.de/doc/capsman-rolling-upgrade.md # # !! Do not edit this file, it is generated from template! -:local 0 "capsman-rolling-upgrade.capsman"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global LogPrintExit2; -:global ScriptLock; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$ScriptLock $0; + :global LogPrint; + :global ScriptLock; -:local InstalledVersion [ /system/package/update/get installed-version ]; + :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={ - $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; + :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"); } - :delay ($Delay . "s"); } +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/capsman-rolling-upgrade.template.rsc b/capsman-rolling-upgrade.template.rsc index 0890ad2..0b1cc2b 100644 --- a/capsman-rolling-upgrade.template.rsc +++ b/capsman-rolling-upgrade.template.rsc @@ -1,48 +1,58 @@ #!rsc by RouterOS # RouterOS script: capsman-rolling-upgrade%TEMPL% -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> # Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# https://rsc.eworm.de/COPYING.md # -# provides: capsman-rolling-upgrade +# provides: capsman-rolling-upgrade%TEMPL% +# requires RouterOS, version=7.15 # # upgrade CAPs one after another -# https://git.eworm.de/cgit/routeros-scripts/about/doc/capsman-rolling-upgrade.md +# https://rsc.eworm.de/doc/capsman-rolling-upgrade.md # -# !! This is just a template! Replace '%PATH%' with 'caps-man', -# !! 'interface/wireless' or 'interface/wifiwave2'! +# !! This is just a template to generate the real script! +# !! Pattern '%TEMPL%' is replaced, paths are filtered. -:local 0 "capsman-rolling-upgrade%TEMPL%"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global LogPrintExit2; -:global ScriptLock; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$ScriptLock $0; + :global LogPrint; + :global ScriptLock; -:local InstalledVersion [ /system/package/update/get installed-version ]; + :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/wifiwave2/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/wifiwave2/capsman/remote-cap/find where version!=$InstalledVersion ] do={ - :local RemoteCapVal [ /caps-man/remote-cap/get $RemoteCap ]; - :local RemoteCapVal [ /interface/wifiwave2/capsman/remote-cap/get $RemoteCap ]; - :if ([ :len $RemoteCapVal ] > 1) do={ -# NOT /caps-man # - :set ($RemoteCapVal->"name") ($RemoteCapVal->"common-name"); -# NOT /caps-man # - $LogPrintExit2 info $0 ("Starting upgrade for " . $RemoteCapVal->"name" . \ - " (" . $RemoteCapVal->"identity" . ")...") false; - /caps-man/remote-cap/upgrade $RemoteCap; - /interface/wifiwave2/capsman/remote-cap/upgrade $RemoteCap; - } else={ - $LogPrintExit2 warning $0 ("Remote CAP vanished, skipping upgrade.") false; + :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"); } - :delay ($Delay . "s"); } +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/capsman-rolling-upgrade.wifi.rsc b/capsman-rolling-upgrade.wifi.rsc new file mode 100644 index 0000000..4afdee2 --- /dev/null +++ b/capsman-rolling-upgrade.wifi.rsc @@ -0,0 +1,51 @@ +#!rsc by RouterOS +# RouterOS script: capsman-rolling-upgrade.wifi +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> +# Michael Gisbers <michael@gisbers.de> +# 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/capsman-rolling-upgrade.wifiwave2.rsc b/capsman-rolling-upgrade.wifiwave2.rsc deleted file mode 100644 index c0c08e7..0000000 --- a/capsman-rolling-upgrade.wifiwave2.rsc +++ /dev/null @@ -1,41 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: capsman-rolling-upgrade.wifiwave2 -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md -# -# provides: capsman-rolling-upgrade -# -# upgrade CAPs one after another -# https://git.eworm.de/cgit/routeros-scripts/about/doc/capsman-rolling-upgrade.md -# -# !! Do not edit this file, it is generated from template! - -:local 0 "capsman-rolling-upgrade.wifiwave2"; -: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 [ /interface/wifiwave2/capsman/remote-cap/find ] ]; -:if ($RemoteCapCount > 0) do={ - :local Delay (600 / $RemoteCapCount); - :if ($Delay > 120) do={ :set Delay 120; } - :foreach RemoteCap in=[ /interface/wifiwave2/capsman/remote-cap/find where version!=$InstalledVersion ] do={ - :local RemoteCapVal [ /interface/wifiwave2/capsman/remote-cap/get $RemoteCap ]; - :if ([ :len $RemoteCapVal ] > 1) do={ - :set ($RemoteCapVal->"name") ($RemoteCapVal->"common-name"); - $LogPrintExit2 info $0 ("Starting upgrade for " . $RemoteCapVal->"name" . \ - " (" . $RemoteCapVal->"identity" . ")...") false; - /interface/wifiwave2/capsman/remote-cap/upgrade $RemoteCap; - } else={ - $LogPrintExit2 warning $0 ("Remote CAP vanished, skipping upgrade.") false; - } - :delay ($Delay . "s"); - } -} diff --git a/certificate-renew-issued.rsc b/certificate-renew-issued.rsc index 2b18a76..91a48de 100644 --- a/certificate-renew-issued.rsc +++ b/certificate-renew-issued.rsc @@ -1,41 +1,52 @@ #!rsc by RouterOS # RouterOS script: certificate-renew-issued -# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2019-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # renew locally issued certificates -# https://git.eworm.de/cgit/routeros-scripts/about/doc/certificate-renew-issued.md +# https://rsc.eworm.de/doc/certificate-renew-issued.md -:local 0 "certificate-renew-issued"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global CertIssuedExportPass; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; + + :global CertIssuedExportPass; -:global LogPrintExit2; -:global MkDir; -:global ScriptLock; + :global LogPrint; + :global MkDir; + :global ScriptLock; -$ScriptLock $0; + :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")); - $LogPrintExit2 info $0 ("Issued a new certificate for \"" . $CertVal->"common-name" . \ - "\", exported to \"cert-issued/" . $CertVal->"common-name" . ".p12\".") 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={ - $LogPrintExit2 warning $0 ("Failed creating directory, not exporting certificate.") false; + $LogPrint info $ScriptName ("Issued a new certificate for '" . $CertVal->"common-name" . "'."); } - } else={ - $LogPrintExit2 info $0 ("Issued a new certificate for \"" . $CertVal->"common-name" . "\".") false; } +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/certs/Certum-Trusted-Network-CA.pem b/certs/Certum-Trusted-Network-CA.pem new file mode 100644 index 0000000..a48e706 --- /dev/null +++ b/certs/Certum-Trusted-Network-CA.pem @@ -0,0 +1,29 @@ +# 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/Cloudflare Inc ECC CA-3.pem b/certs/Cloudflare Inc ECC CA-3.pem deleted file mode 100644 index fa91603..0000000 --- a/certs/Cloudflare Inc ECC CA-3.pem +++ /dev/null @@ -1,163 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 0a:37:87:64:5e:5f:b4:8c:22:4e:fd:1b:ed:14:0c:3c - Signature Algorithm: sha256WithRSAEncryption - Issuer: C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root - Validity - Not Before: Jan 27 12:48:08 2020 GMT - Not After : Dec 31 23:59:59 2024 GMT - Subject: C = US, O = "Cloudflare, Inc.", CN = Cloudflare Inc ECC CA-3 - Subject Public Key Info: - Public Key Algorithm: id-ecPublicKey - Public-Key: (256 bit) - pub: - 04:b9:ad:4d:66:99:14:0b:46:ec:1f:81:d1:2a:50: - 1e:9d:03:15:2f:34:12:7d:2d:96:b8:88:38:9b:85: - 5f:8f:bf:bb:4d:ef:61:46:c4:c9:73:d4:24:4f:e0: - ee:1c:ce:6c:b3:51:71:2f:6a:ee:4c:05:09:77:d3: - 72:62:a4:9b:d7 - ASN1 OID: prime256v1 - NIST CURVE: P-256 - X509v3 extensions: - X509v3 Subject Key Identifier: - A5:CE:37:EA:EB:B0:75:0E:94:67:88:B4:45:FA:D9:24:10:87:96:1F - X509v3 Authority Key Identifier: - E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0 - X509v3 Key Usage: critical - Digital Signature, Certificate Sign, CRL Sign - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication - X509v3 Basic Constraints: critical - CA:TRUE, pathlen:0 - Authority Information Access: - OCSP - URI:http://ocsp.digicert.com - X509v3 CRL Distribution Points: - Full Name: - URI:http://crl3.digicert.com/Omniroot2025.crl - X509v3 Certificate Policies: - Policy: 2.16.840.1.114412.1.1 - CPS: https://www.digicert.com/CPS - Policy: 2.16.840.1.114412.1.2 - Policy: 2.23.140.1.2.1 - Policy: 2.23.140.1.2.2 - Policy: 2.23.140.1.2.3 - Signature Algorithm: sha256WithRSAEncryption - Signature Value: - 05:24:1d:dd:1b:b0:2a:eb:98:d6:85:e3:39:4d:5e:6b:57:9d: - 82:57:fc:eb:e8:31:a2:57:90:65:05:be:16:44:38:5a:77:02: - b9:cf:10:42:c6:e1:92:a4:e3:45:27:f8:00:47:2c:68:a8:56: - 99:53:54:8f:ad:9e:40:c1:d0:0f:b6:d7:0d:0b:38:48:6c:50: - 2c:49:90:06:5b:64:1d:8b:cc:48:30:2e:de:08:e2:9b:49:22: - c0:92:0c:11:5e:96:92:94:d5:fc:20:dc:56:6c:e5:92:93:bf: - 7a:1c:c0:37:e3:85:49:15:fa:2b:e1:74:39:18:0f:b7:da:f3: - a2:57:58:60:4f:cc:8e:94:00:fc:46:7b:34:31:3e:4d:47:82: - 81:3a:cb:f4:89:5d:0e:ef:4d:0d:6e:9c:1b:82:24:dd:32:25: - 5d:11:78:51:10:3d:a0:35:23:04:2f:65:6f:9c:c1:d1:43:d7: - d0:1e:f3:31:67:59:27:dd:6b:d2:75:09:93:11:24:24:14:cf: - 29:be:e6:23:c3:b8:8f:72:3f:e9:07:c8:24:44:53:7a:b3:b9: - 61:65:a1:4c:0e:c6:48:00:c9:75:63:05:87:70:45:52:83:d3: - 95:9d:45:ea:f0:e8:31:1d:7e:09:1f:0a:fe:3e:dd:aa:3c:5e: - 74:d2:ac:b1 ------BEGIN CERTIFICATE----- -MIIDzTCCArWgAwIBAgIQCjeHZF5ftIwiTv0b7RQMPDANBgkqhkiG9w0BAQsFADBa -MQswCQYDVQQGEwJJRTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJl -clRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTIw -MDEyNzEyNDgwOFoXDTI0MTIzMTIzNTk1OVowSjELMAkGA1UEBhMCVVMxGTAXBgNV -BAoTEENsb3VkZmxhcmUsIEluYy4xIDAeBgNVBAMTF0Nsb3VkZmxhcmUgSW5jIEVD -QyBDQS0zMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEua1NZpkUC0bsH4HRKlAe -nQMVLzQSfS2WuIg4m4Vfj7+7Te9hRsTJc9QkT+DuHM5ss1FxL2ruTAUJd9NyYqSb -16OCAWgwggFkMB0GA1UdDgQWBBSlzjfq67B1DpRniLRF+tkkEIeWHzAfBgNVHSME -GDAWgBTlnVkwgkdYzKz6CFQ2hns6tQRN8DAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0l -BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAwNAYI -KwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j -b20wOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL09t -bmlyb290MjAyNS5jcmwwbQYDVR0gBGYwZDA3BglghkgBhv1sAQEwKjAoBggrBgEF -BQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzALBglghkgBhv1sAQIw -CAYGZ4EMAQIBMAgGBmeBDAECAjAIBgZngQwBAgMwDQYJKoZIhvcNAQELBQADggEB -AAUkHd0bsCrrmNaF4zlNXmtXnYJX/OvoMaJXkGUFvhZEOFp3ArnPEELG4ZKk40Un -+ABHLGioVplTVI+tnkDB0A+21w0LOEhsUCxJkAZbZB2LzEgwLt4I4ptJIsCSDBFe -lpKU1fwg3FZs5ZKTv3ocwDfjhUkV+ivhdDkYD7fa86JXWGBPzI6UAPxGezQxPk1H -goE6y/SJXQ7vTQ1unBuCJN0yJV0ReFEQPaA1IwQvZW+cwdFD19Ae8zFnWSfda9J1 -CZMRJCQUzym+5iPDuI9yP+kHyCREU3qzuWFloUwOxkgAyXVjBYdwRVKD05WdRerw -6DEdfgkfCv4+3ao8XnTSrLE= ------END CERTIFICATE----- -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 33554617 (0x20000b9) - Signature Algorithm: sha1WithRSAEncryption - Issuer: C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root - Validity - Not Before: May 12 18:46:00 2000 GMT - Not After : May 12 23:59:00 2025 GMT - Subject: C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) - Modulus: - 00:a3:04:bb:22:ab:98:3d:57:e8:26:72:9a:b5:79: - d4:29:e2:e1:e8:95:80:b1:b0:e3:5b:8e:2b:29:9a: - 64:df:a1:5d:ed:b0:09:05:6d:db:28:2e:ce:62:a2: - 62:fe:b4:88:da:12:eb:38:eb:21:9d:c0:41:2b:01: - 52:7b:88:77:d3:1c:8f:c7:ba:b9:88:b5:6a:09:e7: - 73:e8:11:40:a7:d1:cc:ca:62:8d:2d:e5:8f:0b:a6: - 50:d2:a8:50:c3:28:ea:f5:ab:25:87:8a:9a:96:1c: - a9:67:b8:3f:0c:d5:f7:f9:52:13:2f:c2:1b:d5:70: - 70:f0:8f:c0:12:ca:06:cb:9a:e1:d9:ca:33:7a:77: - d6:f8:ec:b9:f1:68:44:42:48:13:d2:c0:c2:a4:ae: - 5e:60:fe:b6:a6:05:fc:b4:dd:07:59:02:d4:59:18: - 98:63:f5:a5:63:e0:90:0c:7d:5d:b2:06:7a:f3:85: - ea:eb:d4:03:ae:5e:84:3e:5f:ff:15:ed:69:bc:f9: - 39:36:72:75:cf:77:52:4d:f3:c9:90:2c:b9:3d:e5: - c9:23:53:3f:1f:24:98:21:5c:07:99:29:bd:c6:3a: - ec:e7:6e:86:3a:6b:97:74:63:33:bd:68:18:31:f0: - 78:8d:76:bf:fc:9e:8e:5d:2a:86:a7:4d:90:dc:27: - 1a:39 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Subject Key Identifier: - E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0 - X509v3 Basic Constraints: critical - CA:TRUE, pathlen:3 - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - Signature Algorithm: sha1WithRSAEncryption - Signature Value: - 85:0c:5d:8e:e4:6f:51:68:42:05:a0:dd:bb:4f:27:25:84:03: - bd:f7:64:fd:2d:d7:30:e3:a4:10:17:eb:da:29:29:b6:79:3f: - 76:f6:19:13:23:b8:10:0a:f9:58:a4:d4:61:70:bd:04:61:6a: - 12:8a:17:d5:0a:bd:c5:bc:30:7c:d6:e9:0c:25:8d:86:40:4f: - ec:cc:a3:7e:38:c6:37:11:4f:ed:dd:68:31:8e:4c:d2:b3:01: - 74:ee:be:75:5e:07:48:1a:7f:70:ff:16:5c:84:c0:79:85:b8: - 05:fd:7f:be:65:11:a3:0f:c0:02:b4:f8:52:37:39:04:d5:a9: - 31:7a:18:bf:a0:2a:f4:12:99:f7:a3:45:82:e3:3c:5e:f5:9d: - 9e:b5:c8:9e:7c:2e:c8:a4:9e:4e:08:14:4b:6d:fd:70:6d:6b: - 1a:63:bd:64:e6:1f:b7:ce:f0:f2:9f:2e:bb:1b:b7:f2:50:88: - 73:92:c2:e2:e3:16:8d:9a:32:02:ab:8e:18:dd:e9:10:11:ee: - 7e:35:ab:90:af:3e:30:94:7a:d0:33:3d:a7:65:0f:f5:fc:8e: - 9e:62:cf:47:44:2c:01:5d:bb:1d:b5:32:d2:47:d2:38:2e:d0: - fe:81:dc:32:6a:1e:b5:ee:3c:d5:fc:e7:81:1d:19:c3:24:42: - ea:63:39:a9 ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ -RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD -VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX -DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y -ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy -VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr -mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr -IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK -mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu -XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy -dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye -jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 -BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 -DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 -9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx -jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 -Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz -ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS -R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp ------END CERTIFICATE----- diff --git a/certs/DigiCert TLS Hybrid ECC SHA384 2020 CA1.pem b/certs/DigiCert TLS Hybrid ECC SHA384 2020 CA1.pem deleted file mode 100644 index 446f56f..0000000 --- a/certs/DigiCert TLS Hybrid ECC SHA384 2020 CA1.pem +++ /dev/null @@ -1,174 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 07:f2:f3:5c:87:a8:77:af:7a:ef:e9:47:99:35:25:bd - Signature Algorithm: sha384WithRSAEncryption - Issuer: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA - Validity - Not Before: Apr 14 00:00:00 2021 GMT - Not After : Apr 13 23:59:59 2031 GMT - Subject: C = US, O = DigiCert Inc, CN = DigiCert TLS Hybrid ECC SHA384 2020 CA1 - Subject Public Key Info: - Public Key Algorithm: id-ecPublicKey - Public-Key: (384 bit) - pub: - 04:c1:1b:c6:9a:5b:98:d9:a4:29:a0:e9:d4:04:b5: - db:eb:a6:b2:6c:55:c0:ff:ed:98:c6:49:2f:06:27: - 51:cb:bf:70:c1:05:7a:c3:b1:9d:87:89:ba:ad:b4: - 13:17:c9:a8:b4:83:c8:b8:90:d1:cc:74:35:36:3c: - 83:72:b0:b5:d0:f7:22:69:c8:f1:80:c4:7b:40:8f: - cf:68:87:26:5c:39:89:f1:4d:91:4d:da:89:8b:e4: - 03:c3:43:e5:bf:2f:73 - ASN1 OID: secp384r1 - NIST CURVE: P-384 - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:TRUE, pathlen:0 - X509v3 Subject Key Identifier: - 0A:BC:08:29:17:8C:A5:39:6D:7A:0E:CE:33:C7:2E:B3:ED:FB:C3:7A - X509v3 Authority Key Identifier: - keyid:03:DE:50:35:56:D1:4C:BB:66:F0:A3:E2:1B:1B:C3:97:B2:3D:D1:55 - - X509v3 Key Usage: critical - Digital Signature, Certificate Sign, CRL Sign - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication - Authority Information Access: - OCSP - URI:http://ocsp.digicert.com - CA Issuers - URI:http://cacerts.digicert.com/DigiCertGlobalRootCA.crt - - X509v3 CRL Distribution Points: - - Full Name: - URI:http://crl3.digicert.com/DigiCertGlobalRootCA.crl - - X509v3 Certificate Policies: - Policy: 2.16.840.1.114412.2.1 - Policy: 2.23.140.1.1 - Policy: 2.23.140.1.2.1 - Policy: 2.23.140.1.2.2 - Policy: 2.23.140.1.2.3 - - Signature Algorithm: sha384WithRSAEncryption - 47:59:81:7f:d4:1b:1f:b0:71:f6:98:5d:18:ba:98:47:98:b0: - 7e:76:2b:ea:ff:1a:8b:ac:26:b3:42:8d:31:e6:4a:e8:19:d0: - ef:da:14:e7:d7:14:92:a1:92:f2:a7:2e:2d:af:fb:1d:f6:fb: - 53:b0:8a:3f:fc:d8:16:0a:e9:b0:2e:b6:a5:0b:18:90:35:26: - a2:da:f6:a8:b7:32:fc:95:23:4b:c6:45:b9:c4:cf:e4:7c:ee: - e6:c9:f8:90:bd:72:e3:99:c3:1d:0b:05:7c:6a:97:6d:b2:ab: - 02:36:d8:c2:bc:2c:01:92:3f:04:a3:8b:75:11:c7:b9:29:bc: - 11:d0:86:ba:92:bc:26:f9:65:c8:37:cd:26:f6:86:13:0c:04: - aa:89:e5:78:b1:c1:4e:79:bc:76:a3:0b:51:e4:c5:d0:9e:6a: - fe:1a:2c:56:ae:06:36:27:a3:73:1c:08:7d:93:32:d0:c2:44: - 19:da:8d:f4:0e:7b:1d:28:03:2b:09:8a:76:ca:77:dc:87:7a: - ac:7b:52:26:55:a7:72:0f:9d:d2:88:4f:fe:b1:21:c5:1a:a1: - aa:39:f5:56:db:c2:84:c4:35:1f:70:da:bb:46:f0:86:bf:64: - 00:c4:3e:f7:9f:46:1b:9d:23:05:b9:7d:b3:4f:0f:a9:45:3a: - e3:74:30:98 ------BEGIN CERTIFICATE----- -MIIEFzCCAv+gAwIBAgIQB/LzXIeod6967+lHmTUlvTANBgkqhkiG9w0BAQwFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaMFYxCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMDAuBgNVBAMTJ0RpZ2lDZXJ0IFRMUyBI -eWJyaWQgRUNDIFNIQTM4NCAyMDIwIENBMTB2MBAGByqGSM49AgEGBSuBBAAiA2IA -BMEbxppbmNmkKaDp1AS12+umsmxVwP/tmMZJLwYnUcu/cMEFesOxnYeJuq20ExfJ -qLSDyLiQ0cx0NTY8g3KwtdD3ImnI8YDEe0CPz2iHJlw5ifFNkU3aiYvkA8ND5b8v -c6OCAYIwggF+MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFAq8CCkXjKU5 -bXoOzjPHLrPt+8N6MB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA4G -A1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYI -KwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j -b20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp -Q2VydEdsb2JhbFJvb3RDQS5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2Ny -bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDA9BgNVHSAE -NjA0MAsGCWCGSAGG/WwCATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgG -BmeBDAECAzANBgkqhkiG9w0BAQwFAAOCAQEAR1mBf9QbH7Bx9phdGLqYR5iwfnYr -6v8ai6wms0KNMeZK6BnQ79oU59cUkqGS8qcuLa/7Hfb7U7CKP/zYFgrpsC62pQsY -kDUmotr2qLcy/JUjS8ZFucTP5Hzu5sn4kL1y45nDHQsFfGqXbbKrAjbYwrwsAZI/ -BKOLdRHHuSm8EdCGupK8JvllyDfNJvaGEwwEqonleLHBTnm8dqMLUeTF0J5q/hos -Vq4GNiejcxwIfZMy0MJEGdqN9A57HSgDKwmKdsp33Id6rHtSJlWncg+d0ohP/rEh -xRqhqjn1VtvChMQ1H3Dau0bwhr9kAMQ+959GG50jBbl9s08PqUU643QwmA== ------END CERTIFICATE----- -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 08:3b:e0:56:90:42:46:b1:a1:75:6a:c9:59:91:c7:4a - Signature Algorithm: sha1WithRSAEncryption - Issuer: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA - Validity - Not Before: Nov 10 00:00:00 2006 GMT - Not After : Nov 10 00:00:00 2031 GMT - Subject: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:e2:3b:e1:11:72:de:a8:a4:d3:a3:57:aa:50:a2: - 8f:0b:77:90:c9:a2:a5:ee:12:ce:96:5b:01:09:20: - cc:01:93:a7:4e:30:b7:53:f7:43:c4:69:00:57:9d: - e2:8d:22:dd:87:06:40:00:81:09:ce:ce:1b:83:bf: - df:cd:3b:71:46:e2:d6:66:c7:05:b3:76:27:16:8f: - 7b:9e:1e:95:7d:ee:b7:48:a3:08:da:d6:af:7a:0c: - 39:06:65:7f:4a:5d:1f:bc:17:f8:ab:be:ee:28:d7: - 74:7f:7a:78:99:59:85:68:6e:5c:23:32:4b:bf:4e: - c0:e8:5a:6d:e3:70:bf:77:10:bf:fc:01:f6:85:d9: - a8:44:10:58:32:a9:75:18:d5:d1:a2:be:47:e2:27: - 6a:f4:9a:33:f8:49:08:60:8b:d4:5f:b4:3a:84:bf: - a1:aa:4a:4c:7d:3e:cf:4f:5f:6c:76:5e:a0:4b:37: - 91:9e:dc:22:e6:6d:ce:14:1a:8e:6a:cb:fe:cd:b3: - 14:64:17:c7:5b:29:9e:32:bf:f2:ee:fa:d3:0b:42: - d4:ab:b7:41:32:da:0c:d4:ef:f8:81:d5:bb:8d:58: - 3f:b5:1b:e8:49:28:a2:70:da:31:04:dd:f7:b2:16: - f2:4c:0a:4e:07:a8:ed:4a:3d:5e:b5:7f:a3:90:c3: - af:27 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Key Usage: critical - Digital Signature, Certificate Sign, CRL Sign - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Subject Key Identifier: - 03:DE:50:35:56:D1:4C:BB:66:F0:A3:E2:1B:1B:C3:97:B2:3D:D1:55 - X509v3 Authority Key Identifier: - keyid:03:DE:50:35:56:D1:4C:BB:66:F0:A3:E2:1B:1B:C3:97:B2:3D:D1:55 - - Signature Algorithm: sha1WithRSAEncryption - cb:9c:37:aa:48:13:12:0a:fa:dd:44:9c:4f:52:b0:f4:df:ae: - 04:f5:79:79:08:a3:24:18:fc:4b:2b:84:c0:2d:b9:d5:c7:fe: - f4:c1:1f:58:cb:b8:6d:9c:7a:74:e7:98:29:ab:11:b5:e3:70: - a0:a1:cd:4c:88:99:93:8c:91:70:e2:ab:0f:1c:be:93:a9:ff: - 63:d5:e4:07:60:d3:a3:bf:9d:5b:09:f1:d5:8e:e3:53:f4:8e: - 63:fa:3f:a7:db:b4:66:df:62:66:d6:d1:6e:41:8d:f2:2d:b5: - ea:77:4a:9f:9d:58:e2:2b:59:c0:40:23:ed:2d:28:82:45:3e: - 79:54:92:26:98:e0:80:48:a8:37:ef:f0:d6:79:60:16:de:ac: - e8:0e:cd:6e:ac:44:17:38:2f:49:da:e1:45:3e:2a:b9:36:53: - cf:3a:50:06:f7:2e:e8:c4:57:49:6c:61:21:18:d5:04:ad:78: - 3c:2c:3a:80:6b:a7:eb:af:15:14:e9:d8:89:c1:b9:38:6c:e2: - 91:6c:8a:ff:64:b9:77:25:57:30:c0:1b:24:a3:e1:dc:e9:df: - 47:7c:b5:b4:24:08:05:30:ec:2d:bd:0b:bf:45:bf:50:b9:a9: - f3:eb:98:01:12:ad:c8:88:c6:98:34:5f:8d:0a:3c:c6:e9:d5: - 95:95:6d:de ------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 new file mode 100644 index 0000000..8af6c7a --- /dev/null +++ b/certs/DigiCert-Global-Root-G2.pem @@ -0,0 +1,29 @@ +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- diff --git a/certs/DigiCert-Global-Root-G3.pem b/certs/DigiCert-Global-Root-G3.pem new file mode 100644 index 0000000..12324dc --- /dev/null +++ b/certs/DigiCert-Global-Root-G3.pem @@ -0,0 +1,22 @@ +# 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/E1.pem b/certs/E1.pem deleted file mode 100644 index 4c3c212..0000000 --- a/certs/E1.pem +++ /dev/null @@ -1,243 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - b3:bd:df:f8:a7:84:5b:bc:e9:03:a0:41:35:b3:4a:45 - Signature Algorithm: ecdsa-with-SHA384 - Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X2 - Validity - Not Before: Sep 4 00:00:00 2020 GMT - Not After : Sep 15 16:00:00 2025 GMT - Subject: C = US, O = Let's Encrypt, CN = E1 - Subject Public Key Info: - Public Key Algorithm: id-ecPublicKey - Public-Key: (384 bit) - pub: - 04:24:5c:2d:a2:2a:fd:1c:4b:a6:5d:97:73:27:31: - ac:b2:a0:69:62:ef:65:e8:a6:b0:f0:ac:4b:9f:ff: - 1c:0b:70:0f:d3:98:2f:4d:fc:0f:00:9b:37:f0:74: - 05:57:32:97:2e:05:ef:2a:43:25:a3:fb:6e:34:27: - 13:f6:4f:7e:69:d3:02:99:5e:eb:24:47:92:c1:24: - 9b:e6:b1:21:8f:c1:24:81:fc:68:cc:1f:69:ba:58: - f5:19:22:f7:74:c6:16 - ASN1 OID: secp384r1 - NIST CURVE: P-384 - X509v3 extensions: - X509v3 Key Usage: critical - Digital Signature, Certificate Sign, CRL Sign - X509v3 Extended Key Usage: - TLS Web Client Authentication, TLS Web Server Authentication - X509v3 Basic Constraints: critical - CA:TRUE, pathlen:0 - X509v3 Subject Key Identifier: - 5A:F3:ED:2B:FC:36:C2:37:79:B9:52:30:EA:54:6F:CF:55:CB:2E:AC - X509v3 Authority Key Identifier: - keyid:7C:42:96:AE:DE:4B:48:3B:FA:92:F8:9E:8C:CF:6D:8B:A9:72:37:95 - - Authority Information Access: - CA Issuers - URI:http://x2.i.lencr.org/ - - X509v3 CRL Distribution Points: - - Full Name: - URI:http://x2.c.lencr.org/ - - X509v3 Certificate Policies: - Policy: 2.23.140.1.2.1 - Policy: 1.3.6.1.4.1.44947.1.1.1 - - Signature Algorithm: ecdsa-with-SHA384 - 30:64:02:30:7b:74:d5:52:13:8d:61:fe:0d:ba:3f:03:00:9d: - f3:d7:98:84:d9:57:2e:bd:e9:0f:9c:5c:48:04:21:f2:cb:b3: - 60:72:8e:97:d6:12:4f:ca:44:f6:42:c9:d3:7b:86:a9:02:30: - 5a:b1:b1:b4:ed:ea:60:99:20:b1:38:03:ca:3d:a0:26:b8:ee: - 6e:2d:4a:f6:c6:66:1f:33:9a:db:92:4a:d5:f5:29:13:c6:70: - 62:28:ba:23:8c:cf:3d:2f:cb:82:e9:7f ------BEGIN CERTIFICATE----- -MIICxjCCAk2gAwIBAgIRALO93/inhFu86QOgQTWzSkUwCgYIKoZIzj0EAwMwTzEL -MAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNo -IEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDIwHhcNMjAwOTA0MDAwMDAwWhcN -MjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3MgRW5j -cnlwdDELMAkGA1UEAxMCRTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQkXC2iKv0c -S6Zdl3MnMayyoGli72XoprDwrEuf/xwLcA/TmC9N/A8AmzfwdAVXMpcuBe8qQyWj -+240JxP2T35p0wKZXuskR5LBJJvmsSGPwSSB/GjMH2m6WPUZIvd0xhajggEIMIIB -BDAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB -MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFFrz7Sv8NsI3eblSMOpUb89V -yy6sMB8GA1UdIwQYMBaAFHxClq7eS0g7+pL4nozPbYupcjeVMDIGCCsGAQUFBwEB -BCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3gyLmkubGVuY3Iub3JnLzAnBgNVHR8E -IDAeMBygGqAYhhZodHRwOi8veDIuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYG -Z4EMAQIBMA0GCysGAQQBgt8TAQEBMAoGCCqGSM49BAMDA2cAMGQCMHt01VITjWH+ -Dbo/AwCd89eYhNlXLr3pD5xcSAQh8suzYHKOl9YST8pE9kLJ03uGqQIwWrGxtO3q -YJkgsTgDyj2gJrjubi1K9sZmHzOa25JK1fUpE8ZwYii6I4zPPS/Lgul/ ------END CERTIFICATE----- -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 41:d2:9d:d1:72:ea:ee:a7:80:c1:2c:6c:e9:2f:87:52 - Signature Algorithm: ecdsa-with-SHA384 - Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X2 - Validity - Not Before: Sep 4 00:00:00 2020 GMT - Not After : Sep 17 16:00:00 2040 GMT - Subject: C = US, O = Internet Security Research Group, CN = ISRG Root X2 - Subject Public Key Info: - Public Key Algorithm: id-ecPublicKey - Public-Key: (384 bit) - pub: - 04:cd:9b:d5:9f:80:83:0a:ec:09:4a:f3:16:4a:3e: - 5c:cf:77:ac:de:67:05:0d:1d:07:b6:dc:16:fb:5a: - 8b:14:db:e2:71:60:c4:ba:45:95:11:89:8e:ea:06: - df:f7:2a:16:1c:a4:b9:c5:c5:32:e0:03:e0:1e:82: - 18:38:8b:d7:45:d8:0a:6a:6e:e6:00:77:fb:02:51: - 7d:22:d8:0a:6e:9a:5b:77:df:f0:fa:41:ec:39:dc: - 75:ca:68:07:0c:1f:ea - ASN1 OID: secp384r1 - NIST CURVE: P-384 - X509v3 extensions: - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Subject Key Identifier: - 7C:42:96:AE:DE:4B:48:3B:FA:92:F8:9E:8C:CF:6D:8B:A9:72:37:95 - Signature Algorithm: ecdsa-with-SHA384 - 30:65:02:30:7b:79:4e:46:50:84:c2:44:87:46:1b:45:70:ff: - 58:99:de:f4:fd:a4:d2:55:a6:20:2d:74:d6:34:bc:41:a3:50: - 5f:01:27:56:b4:be:27:75:06:af:12:2e:75:98:8d:fc:02:31: - 00:8b:f5:77:6c:d4:c8:65:aa:e0:0b:2c:ee:14:9d:27:37:a4: - f9:53:a5:51:e4:29:83:d7:f8:90:31:5b:42:9f:0a:f5:fe:ae: - 00:68:e7:8c:49:0f:b6:6f:5b:5b:15:f2:e7 ------BEGIN CERTIFICATE----- -MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw -CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg -R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 -MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT -ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw -EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW -+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 -ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T -AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI -zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW -tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 -/q4AaOeMSQ+2b1tbFfLn ------END CERTIFICATE----- -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 82:10:cf:b0:d2:40:e3:59:44:63:e0:bb:63:82:8b:00 - Signature Algorithm: sha256WithRSAEncryption - Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X1 - Validity - Not Before: Jun 4 11:04:38 2015 GMT - Not After : Jun 4 11:04:38 2035 GMT - Subject: C = US, O = Internet Security Research Group, CN = ISRG Root X1 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (4096 bit) - Modulus: - 00:ad:e8:24:73:f4:14:37:f3:9b:9e:2b:57:28:1c: - 87:be:dc:b7:df:38:90:8c:6e:3c:e6:57:a0:78:f7: - 75:c2:a2:fe:f5:6a:6e:f6:00:4f:28:db:de:68:86: - 6c:44:93:b6:b1:63:fd:14:12:6b:bf:1f:d2:ea:31: - 9b:21:7e:d1:33:3c:ba:48:f5:dd:79:df:b3:b8:ff: - 12:f1:21:9a:4b:c1:8a:86:71:69:4a:66:66:6c:8f: - 7e:3c:70:bf:ad:29:22:06:f3:e4:c0:e6:80:ae:e2: - 4b:8f:b7:99:7e:94:03:9f:d3:47:97:7c:99:48:23: - 53:e8:38:ae:4f:0a:6f:83:2e:d1:49:57:8c:80:74: - b6:da:2f:d0:38:8d:7b:03:70:21:1b:75:f2:30:3c: - fa:8f:ae:dd:da:63:ab:eb:16:4f:c2:8e:11:4b:7e: - cf:0b:e8:ff:b5:77:2e:f4:b2:7b:4a:e0:4c:12:25: - 0c:70:8d:03:29:a0:e1:53:24:ec:13:d9:ee:19:bf: - 10:b3:4a:8c:3f:89:a3:61:51:de:ac:87:07:94:f4: - 63:71:ec:2e:e2:6f:5b:98:81:e1:89:5c:34:79:6c: - 76:ef:3b:90:62:79:e6:db:a4:9a:2f:26:c5:d0:10: - e1:0e:de:d9:10:8e:16:fb:b7:f7:a8:f7:c7:e5:02: - 07:98:8f:36:08:95:e7:e2:37:96:0d:36:75:9e:fb: - 0e:72:b1:1d:9b:bc:03:f9:49:05:d8:81:dd:05:b4: - 2a:d6:41:e9:ac:01:76:95:0a:0f:d8:df:d5:bd:12: - 1f:35:2f:28:17:6c:d2:98:c1:a8:09:64:77:6e:47: - 37:ba:ce:ac:59:5e:68:9d:7f:72:d6:89:c5:06:41: - 29:3e:59:3e:dd:26:f5:24:c9:11:a7:5a:a3:4c:40: - 1f:46:a1:99:b5:a7:3a:51:6e:86:3b:9e:7d:72:a7: - 12:05:78:59:ed:3e:51:78:15:0b:03:8f:8d:d0:2f: - 05:b2:3e:7b:4a:1c:4b:73:05:12:fc:c6:ea:e0:50: - 13:7c:43:93:74:b3:ca:74:e7:8e:1f:01:08:d0:30: - d4:5b:71:36:b4:07:ba:c1:30:30:5c:48:b7:82:3b: - 98:a6:7d:60:8a:a2:a3:29:82:cc:ba:bd:83:04:1b: - a2:83:03:41:a1:d6:05:f1:1b:c2:b6:f0:a8:7c:86: - 3b:46:a8:48:2a:88:dc:76:9a:76:bf:1f:6a:a5:3d: - 19:8f:eb:38:f3:64:de:c8:2b:0d:0a:28:ff:f7:db: - e2:15:42:d4:22:d0:27:5d:e1:79:fe:18:e7:70:88: - ad:4e:e6:d9:8b:3a:c6:dd:27:51:6e:ff:bc:64:f5: - 33:43:4f - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Subject Key Identifier: - 79:B4:59:E6:7B:B6:E5:E4:01:73:80:08:88:C8:1A:58:F6:E9:9B:6E - Signature Algorithm: sha256WithRSAEncryption - 55:1f:58:a9:bc:b2:a8:50:d0:0c:b1:d8:1a:69:20:27:29:08: - ac:61:75:5c:8a:6e:f8:82:e5:69:2f:d5:f6:56:4b:b9:b8:73: - 10:59:d3:21:97:7e:e7:4c:71:fb:b2:d2:60:ad:39:a8:0b:ea: - 17:21:56:85:f1:50:0e:59:eb:ce:e0:59:e9:ba:c9:15:ef:86: - 9d:8f:84:80:f6:e4:e9:91:90:dc:17:9b:62:1b:45:f0:66:95: - d2:7c:6f:c2:ea:3b:ef:1f:cf:cb:d6:ae:27:f1:a9:b0:c8:ae: - fd:7d:7e:9a:fa:22:04:eb:ff:d9:7f:ea:91:2b:22:b1:17:0e: - 8f:f2:8a:34:5b:58:d8:fc:01:c9:54:b9:b8:26:cc:8a:88:33: - 89:4c:2d:84:3c:82:df:ee:96:57:05:ba:2c:bb:f7:c4:b7:c7: - 4e:3b:82:be:31:c8:22:73:73:92:d1:c2:80:a4:39:39:10:33: - 23:82:4c:3c:9f:86:b2:55:98:1d:be:29:86:8c:22:9b:9e:e2: - 6b:3b:57:3a:82:70:4d:dc:09:c7:89:cb:0a:07:4d:6c:e8:5d: - 8e:c9:ef:ce:ab:c7:bb:b5:2b:4e:45:d6:4a:d0:26:cc:e5:72: - ca:08:6a:a5:95:e3:15:a1:f7:a4:ed:c9:2c:5f:a5:fb:ff:ac: - 28:02:2e:be:d7:7b:bb:e3:71:7b:90:16:d3:07:5e:46:53:7c: - 37:07:42:8c:d3:c4:96:9c:d5:99:b5:2a:e0:95:1a:80:48:ae: - 4c:39:07:ce:cc:47:a4:52:95:2b:ba:b8:fb:ad:d2:33:53:7d: - e5:1d:4d:6d:d5:a1:b1:c7:42:6f:e6:40:27:35:5c:a3:28:b7: - 07:8d:e7:8d:33:90:e7:23:9f:fb:50:9c:79:6c:46:d5:b4:15: - b3:96:6e:7e:9b:0c:96:3a:b8:52:2d:3f:d6:5b:e1:fb:08:c2: - 84:fe:24:a8:a3:89:da:ac:6a:e1:18:2a:b1:a8:43:61:5b:d3: - 1f:dc:3b:8d:76:f2:2d:e8:8d:75:df:17:33:6c:3d:53:fb:7b: - cb:41:5f:ff:dc:a2:d0:61:38:e1:96:b8:ac:5d:8b:37:d7:75: - d5:33:c0:99:11:ae:9d:41:c1:72:75:84:be:02:41:42:5f:67: - 24:48:94:d1:9b:27:be:07:3f:b9:b8:4f:81:74:51:e1:7a:b7: - ed:9d:23:e2:be:e0:d5:28:04:13:3c:31:03:9e:dd:7a:6c:8f: - c6:07:18:c6:7f:de:47:8e:3f:28:9e:04:06:cf:a5:54:34:77: - bd:ec:89:9b:e9:17:43:df:5b:db:5f:fe:8e:1e:57:a2:cd:40: - 9d:7e:62:22:da:de:18:27 ------BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- diff --git a/certs/GTS CA 1C3.pem b/certs/GTS CA 1C3.pem deleted file mode 100644 index a8432d2..0000000 --- a/certs/GTS CA 1C3.pem +++ /dev/null @@ -1,242 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 02:03:bc:53:59:6b:34:c7:18:f5:01:50:66 - Signature Algorithm: sha256WithRSAEncryption - Issuer: C = US, O = Google Trust Services LLC, CN = GTS Root R1 - Validity - Not Before: Aug 13 00:00:42 2020 GMT - Not After : Sep 30 00:00:42 2027 GMT - Subject: C = US, O = Google Trust Services LLC, CN = GTS CA 1C3 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:f5:88:df:e7:62:8c:1e:37:f8:37:42:90:7f:6c: - 87:d0:fb:65:82:25:fd:e8:cb:6b:a4:ff:6d:e9:5a: - 23:e2:99:f6:1c:e9:92:03:99:13:7c:09:0a:8a:fa: - 42:d6:5e:56:24:aa:7a:33:84:1f:d1:e9:69:bb:b9: - 74:ec:57:4c:66:68:93:77:37:55:53:fe:39:10:4d: - b7:34:bb:5f:25:77:37:3b:17:94:ea:3c:e5:9d:d5: - bc:c3:b4:43:eb:2e:a7:47:ef:b0:44:11:63:d8:b4: - 41:85:dd:41:30:48:93:1b:bf:b7:f6:e0:45:02:21: - e0:96:42:17:cf:d9:2b:65:56:34:07:26:04:0d:a8: - fd:7d:ca:2e:ef:ea:48:7c:37:4d:3f:00:9f:83:df: - ef:75:84:2e:79:57:5c:fc:57:6e:1a:96:ff:fc:8c: - 9a:a6:99:be:25:d9:7f:96:2c:06:f7:11:2a:02:80: - 80:eb:63:18:3c:50:49:87:e5:8a:ca:5f:19:2b:59: - 96:81:00:a0:fb:51:db:ca:77:0b:0b:c9:96:4f:ef: - 70:49:c7:5c:6d:20:fd:99:b4:b4:e2:ca:2e:77:fd: - 2d:dc:0b:b6:6b:13:0c:8c:19:2b:17:96:98:b9:f0: - 8b:f6:a0:27:bb:b6:e3:8d:51:8f:bd:ae:c7:9b:b1: - 89:9d - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Key Usage: critical - Digital Signature, Certificate Sign, CRL Sign - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication - X509v3 Basic Constraints: critical - CA:TRUE, pathlen:0 - X509v3 Subject Key Identifier: - 8A:74:7F:AF:85:CD:EE:95:CD:3D:9C:D0:E2:46:14:F3:71:35:1D:27 - X509v3 Authority Key Identifier: - keyid:E4:AF:2B:26:71:1A:2B:48:27:85:2F:52:66:2C:EF:F0:89:13:71:3E - - Authority Information Access: - OCSP - URI:http://ocsp.pki.goog/gtsr1 - CA Issuers - URI:http://pki.goog/repo/certs/gtsr1.der - - X509v3 CRL Distribution Points: - - Full Name: - URI:http://crl.pki.goog/gtsr1/gtsr1.crl - - X509v3 Certificate Policies: - Policy: 1.3.6.1.4.1.11129.2.5.3 - CPS: https://pki.goog/repository/ - Policy: 2.23.140.1.2.1 - Policy: 2.23.140.1.2.2 - - Signature Algorithm: sha256WithRSAEncryption - 89:7d:ac:20:5c:0c:3c:be:9a:a8:57:95:1b:b4:ae:fa:ab:a5: - 72:71:b4:36:95:fd:df:40:11:03:4c:c2:46:14:bb:14:24:ab: - f0:50:71:22:db:ad:c4:6e:7f:cf:f1:6a:6f:c8:83:1b:d8:ce: - 89:5f:87:6c:87:b8:a9:0c:a3:9b:a1:62:94:93:95:df:5b:ae: - 66:19:0b:02:96:9e:fc:b5:e7:10:69:3e:7a:cb:46:49:5f:46: - e1:41:b1:d7:98:4d:65:34:00:80:1a:3f:4f:9f:6c:7f:49:00: - 81:53:41:a4:92:21:82:82:1a:f1:a3:44:5b:2a:50:12:13:4d: - c1:53:36:f3:42:08:af:54:fa:8e:77:53:1b:64:38:27:17:09: - bd:58:c9:1b:7c:39:2d:5b:f3:ce:d4:ed:97:db:14:03:bf:09: - 53:24:1f:c2:0c:04:79:98:26:f2:61:f1:53:52:fd:42:8c:1b: - 66:2b:3f:15:a1:bb:ff:f6:9b:e3:81:9a:01:06:71:89:35:28: - 24:dd:e1:bd:eb:19:2d:e1:48:cb:3d:59:83:51:b4:74:c6:9d: - 7c:c6:b1:86:5b:af:cc:34:c4:d3:cc:d4:81:11:95:00:a1:f4: - 12:22:01:fa:b4:83:71:af:8c:b7:8c:73:24:ac:37:53:c2:00: - 90:3f:11:fe:5c:ed:36:94:10:3b:bd:29:ae:e2:c7:3a:62:3b: - 6c:63:d9:80:bf:59:71:ac:63:27:b9:4c:17:a0:da:f6:73:15: - bf:2a:de:8f:f3:a5:6c:32:81:33:03:d0:86:51:71:99:34:ba: - 93:8d:5d:b5:51:58:f7:b2:93:e8:01:f6:59:be:71:9b:fd:4d: - 28:ce:cf:6d:c7:16:dc:f7:d1:d6:46:9b:a7:ca:6b:e9:77:0f: - fd:a0:b6:1b:23:83:1d:10:1a:d9:09:00:84:e0:44:d3:a2:75: - 23:b3:34:86:f6:20:b0:a4:5e:10:1d:e0:52:46:00:9d:b1:0f: - 1f:21:70:51:f5:9a:dd:06:fc:55:f4:2b:0e:33:77:c3:4b:42: - c2:f1:77:13:fc:73:80:94:eb:1f:bb:37:3f:ce:02:2a:66:b0: - 73:1d:32:a5:32:6c:32:b0:8e:e0:c4:23:ff:5b:7d:4d:65:70: - ac:2b:9b:3d:ce:db:e0:6d:8e:32:80:be:96:9f:92:63:bc:97: - bb:5d:b9:f4:e1:71:5e:2a:e4:ef:03:22:b1:8a:65:3a:8f:c0: - 93:65:d4:85:cd:0f:0f:5b:83:59:16:47:16:2d:9c:24:3a:c8: - 80:a6:26:14:85:9b:f6:37:9b:ac:6f:f9:c5:c3:06:51:f3:e2: - 7f:c5:b1:10:ba:51:f4:dd ------BEGIN CERTIFICATE----- -MIIFljCCA36gAwIBAgINAgO8U1lrNMcY9QFQZjANBgkqhkiG9w0BAQsFADBHMQsw -CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU -MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMjAwODEzMDAwMDQyWhcNMjcwOTMwMDAw -MDQyWjBGMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp -Y2VzIExMQzETMBEGA1UEAxMKR1RTIENBIDFDMzCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAPWI3+dijB43+DdCkH9sh9D7ZYIl/ejLa6T/belaI+KZ9hzp -kgOZE3wJCor6QtZeViSqejOEH9Hpabu5dOxXTGZok3c3VVP+ORBNtzS7XyV3NzsX -lOo85Z3VvMO0Q+sup0fvsEQRY9i0QYXdQTBIkxu/t/bgRQIh4JZCF8/ZK2VWNAcm -BA2o/X3KLu/qSHw3TT8An4Pf73WELnlXXPxXbhqW//yMmqaZviXZf5YsBvcRKgKA -gOtjGDxQSYflispfGStZloEAoPtR28p3CwvJlk/vcEnHXG0g/Zm0tOLKLnf9LdwL -tmsTDIwZKxeWmLnwi/agJ7u2441Rj72ux5uxiZ0CAwEAAaOCAYAwggF8MA4GA1Ud -DwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0T -AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUinR/r4XN7pXNPZzQ4kYU83E1HScwHwYD -VR0jBBgwFoAU5K8rJnEaK0gnhS9SZizv8IkTcT4waAYIKwYBBQUHAQEEXDBaMCYG -CCsGAQUFBzABhhpodHRwOi8vb2NzcC5wa2kuZ29vZy9ndHNyMTAwBggrBgEFBQcw -AoYkaHR0cDovL3BraS5nb29nL3JlcG8vY2VydHMvZ3RzcjEuZGVyMDQGA1UdHwQt -MCswKaAnoCWGI2h0dHA6Ly9jcmwucGtpLmdvb2cvZ3RzcjEvZ3RzcjEuY3JsMFcG -A1UdIARQME4wOAYKKwYBBAHWeQIFAzAqMCgGCCsGAQUFBwIBFhxodHRwczovL3Br -aS5nb29nL3JlcG9zaXRvcnkvMAgGBmeBDAECATAIBgZngQwBAgIwDQYJKoZIhvcN -AQELBQADggIBAIl9rCBcDDy+mqhXlRu0rvqrpXJxtDaV/d9AEQNMwkYUuxQkq/BQ -cSLbrcRuf8/xam/IgxvYzolfh2yHuKkMo5uhYpSTld9brmYZCwKWnvy15xBpPnrL -RklfRuFBsdeYTWU0AIAaP0+fbH9JAIFTQaSSIYKCGvGjRFsqUBITTcFTNvNCCK9U -+o53UxtkOCcXCb1YyRt8OS1b887U7ZfbFAO/CVMkH8IMBHmYJvJh8VNS/UKMG2Yr -PxWhu//2m+OBmgEGcYk1KCTd4b3rGS3hSMs9WYNRtHTGnXzGsYZbr8w0xNPM1IER -lQCh9BIiAfq0g3GvjLeMcySsN1PCAJA/Ef5c7TaUEDu9Ka7ixzpiO2xj2YC/WXGs -Yye5TBeg2vZzFb8q3o/zpWwygTMD0IZRcZk0upONXbVRWPeyk+gB9lm+cZv9TSjO -z23HFtz30dZGm6fKa+l3D/2gthsjgx0QGtkJAITgRNOidSOzNIb2ILCkXhAd4FJG -AJ2xDx8hcFH1mt0G/FX0Kw4zd8NLQsLxdxP8c4CU6x+7Nz/OAipmsHMdMqUybDKw -juDEI/9bfU1lcKwrmz3O2+BtjjKAvpafkmO8l7tdufThcV4q5O8DIrGKZTqPwJNl -1IXNDw9bg1kWRxYtnCQ6yICmJhSFm/Y3m6xv+cXDBlHz4n/FsRC6UfTd ------END CERTIFICATE----- -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 6e:47:a9:c5:4b:47:0c:0d:ec:33:d0:89:b9:1c:f4:e1 - Signature Algorithm: sha384WithRSAEncryption - Issuer: C = US, O = Google Trust Services LLC, CN = GTS Root R1 - Validity - Not Before: Jun 22 00:00:00 2016 GMT - Not After : Jun 22 00:00:00 2036 GMT - Subject: C = US, O = Google Trust Services LLC, CN = GTS Root R1 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (4096 bit) - Modulus: - 00:b6:11:02:8b:1e:e3:a1:77:9b:3b:dc:bf:94:3e: - b7:95:a7:40:3c:a1:fd:82:f9:7d:32:06:82:71:f6: - f6:8c:7f:fb:e8:db:bc:6a:2e:97:97:a3:8c:4b:f9: - 2b:f6:b1:f9:ce:84:1d:b1:f9:c5:97:de:ef:b9:f2: - a3:e9:bc:12:89:5e:a7:aa:52:ab:f8:23:27:cb:a4: - b1:9c:63:db:d7:99:7e:f0:0a:5e:eb:68:a6:f4:c6: - 5a:47:0d:4d:10:33:e3:4e:b1:13:a3:c8:18:6c:4b: - ec:fc:09:90:df:9d:64:29:25:23:07:a1:b4:d2:3d: - 2e:60:e0:cf:d2:09:87:bb:cd:48:f0:4d:c2:c2:7a: - 88:8a:bb:ba:cf:59:19:d6:af:8f:b0:07:b0:9e:31: - f1:82:c1:c0:df:2e:a6:6d:6c:19:0e:b5:d8:7e:26: - 1a:45:03:3d:b0:79:a4:94:28:ad:0f:7f:26:e5:a8: - 08:fe:96:e8:3c:68:94:53:ee:83:3a:88:2b:15:96: - 09:b2:e0:7a:8c:2e:75:d6:9c:eb:a7:56:64:8f:96: - 4f:68:ae:3d:97:c2:84:8f:c0:bc:40:c0:0b:5c:bd: - f6:87:b3:35:6c:ac:18:50:7f:84:e0:4c:cd:92:d3: - 20:e9:33:bc:52:99:af:32:b5:29:b3:25:2a:b4:48: - f9:72:e1:ca:64:f7:e6:82:10:8d:e8:9d:c2:8a:88: - fa:38:66:8a:fc:63:f9:01:f9:78:fd:7b:5c:77:fa: - 76:87:fa:ec:df:b1:0e:79:95:57:b4:bd:26:ef:d6: - 01:d1:eb:16:0a:bb:8e:0b:b5:c5:c5:8a:55:ab:d3: - ac:ea:91:4b:29:cc:19:a4:32:25:4e:2a:f1:65:44: - d0:02:ce:aa:ce:49:b4:ea:9f:7c:83:b0:40:7b:e7: - 43:ab:a7:6c:a3:8f:7d:89:81:fa:4c:a5:ff:d5:8e: - c3:ce:4b:e0:b5:d8:b3:8e:45:cf:76:c0:ed:40:2b: - fd:53:0f:b0:a7:d5:3b:0d:b1:8a:a2:03:de:31:ad: - cc:77:ea:6f:7b:3e:d6:df:91:22:12:e6:be:fa:d8: - 32:fc:10:63:14:51:72:de:5d:d6:16:93:bd:29:68: - 33:ef:3a:66:ec:07:8a:26:df:13:d7:57:65:78:27: - de:5e:49:14:00:a2:00:7f:9a:a8:21:b6:a9:b1:95: - b0:a5:b9:0d:16:11:da:c7:6c:48:3c:40:e0:7e:0d: - 5a:cd:56:3c:d1:97:05:b9:cb:4b:ed:39:4b:9c:c4: - 3f:d2:55:13:6e:24:b0:d6:71:fa:f4:c1:ba:cc:ed: - 1b:f5:fe:81:41:d8:00:98:3d:3a:c8:ae:7a:98:37: - 18:05:95 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Subject Key Identifier: - E4:AF:2B:26:71:1A:2B:48:27:85:2F:52:66:2C:EF:F0:89:13:71:3E - Signature Algorithm: sha384WithRSAEncryption - 38:96:0a:ee:3d:b4:96:1e:5f:ef:9d:9c:0b:33:9f:2b:e0:ca: - fd:d2:8e:0a:1f:41:74:a5:7c:aa:84:d4:e5:f2:1e:e6:37:52: - 32:9c:0b:d1:61:1d:bf:28:c1:b6:44:29:35:75:77:98:b2:7c: - d9:bd:74:ac:8a:68:e3:a9:31:09:29:01:60:73:e3:47:7c:53: - a8:90:4a:27:ef:4b:d7:9f:93:e7:82:36:ce:9a:68:0c:82:e7: - cf:d4:10:16:6f:5f:0e:99:5c:f6:1f:71:7d:ef:ef:7b:2f:7e: - ea:36:d6:97:70:0b:15:ee:d7:5c:56:6a:33:a5:e3:49:38:0c: - b8:7d:fb:8d:85:a4:b1:59:5e:f4:6a:e1:dd:a1:f6:64:44:ae: - e6:51:83:21:66:c6:11:3e:f3:ce:47:ee:9c:28:1f:25:da:ff: - ac:66:95:dd:35:0f:5c:ef:20:2c:62:fd:91:ba:a9:cc:fc:5a: - 9c:93:81:83:29:97:4a:7c:5a:72:b4:39:d0:b7:77:cb:79:fd: - 69:3a:92:37:ed:6e:38:65:46:7e:e9:60:bd:79:88:97:5f:38: - 12:f4:ee:af:5b:82:c8:86:d5:e1:99:6d:8c:04:f2:76:ba:49: - f6:6e:e9:6d:1e:5f:a0:ef:27:82:76:40:f8:a6:d3:58:5c:0f: - 2c:42:da:42:c6:7b:88:34:c7:c1:d8:45:9b:c1:3e:c5:61:1d: - d9:63:50:49:f6:34:85:6a:e0:18:c5:6e:47:ab:41:42:29:9b: - f6:60:0d:d2:31:d3:63:98:23:93:5a:00:81:48:b4:ef:cd:8a: - cd:c9:cf:99:ee:d9:9e:aa:36:e1:68:4b:71:49:14:36:28:3a: - 3d:1d:ce:9a:8f:25:e6:80:71:61:2b:b5:7b:cc:f9:25:16:81: - e1:31:5f:a1:a3:7e:16:a4:9c:16:6a:97:18:bd:76:72:a5:0b: - 9e:1d:36:e6:2f:a1:2f:be:70:91:0f:a8:e6:da:f8:c4:92:40: - 6c:25:7e:7b:b3:09:dc:b2:17:ad:80:44:f0:68:a5:8f:94:75: - ff:74:5a:e8:a8:02:7c:0c:09:e2:a9:4b:0b:a0:85:0b:62:b9: - ef:a1:31:92:fb:ef:f6:51:04:89:6c:e8:a9:74:a1:bb:17:b3: - b5:fd:49:0f:7c:3c:ec:83:18:20:43:4e:d5:93:ba:b4:34:b1: - 1f:16:36:1f:0c:e6:64:39:16:4c:dc:e0:fe:1d:c8:a9:62:3d: - 40:ea:ca:c5:34:02:b4:ae:89:88:33:35:dc:2c:13:73:d8:27: - f1:d0:72:ee:75:3b:22:de:98:68:66:5b:f1:c6:63:47:55:1c: - ba:a5:08:51:75:a6:48:25 ------BEGIN CERTIFICATE----- -MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH -MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM -QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy -MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl -cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM -f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX -mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7 -zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P -fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc -vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4 -Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp -zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO -Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW -k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+ -DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF -lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW -Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1 -d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z -XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR -gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3 -d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv -J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg -DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM -+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy -F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9 -SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws -E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl ------END CERTIFICATE----- diff --git a/certs/GTS-Root-R1.pem b/certs/GTS-Root-R1.pem new file mode 100644 index 0000000..a6095d2 --- /dev/null +++ b/certs/GTS-Root-R1.pem @@ -0,0 +1,38 @@ +# Issuer: CN=GTS Root R1 O=Google Trust Services LLC +# Subject: CN=GTS Root R1 O=Google Trust Services LLC +# Label: "GTS Root R1" +# Serial: 159662320309726417404178440727 +# MD5 Fingerprint: 05:fe:d0:bf:71:a8:a3:76:63:da:01:e0:d8:52:dc:40 +# SHA1 Fingerprint: e5:8c:1c:c4:91:3b:38:63:4b:e9:10:6e:e3:ad:8e:6b:9d:d9:81:4a +# SHA256 Fingerprint: d9:47:43:2a:bd:e7:b7:fa:90:fc:2e:6b:59:10:1b:12:80:e0:e1:c7:e4:e4:0f:a3:c6:88:7f:ff:57:a7:f4:cf +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo +27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w +Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw +TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl +qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH +szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8 +Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk +MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 +wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p +aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN +VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb +C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy +h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4 +7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J +ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef +MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/ +Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT +6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ +0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm +2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb +bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c +-----END CERTIFICATE----- diff --git a/certs/GTS-Root-R4.pem b/certs/GTS-Root-R4.pem new file mode 100644 index 0000000..16a1c36 --- /dev/null +++ b/certs/GTS-Root-R4.pem @@ -0,0 +1,20 @@ +# Issuer: CN=GTS Root R4 O=Google Trust Services LLC +# Subject: CN=GTS Root R4 O=Google Trust Services LLC +# Label: "GTS Root R4" +# Serial: 159662532700760215368942768210 +# MD5 Fingerprint: 43:96:83:77:19:4d:76:b3:9d:65:52:e4:1d:22:a5:e8 +# SHA1 Fingerprint: 77:d3:03:67:b5:e0:0c:15:f6:0c:38:61:df:7c:e1:3b:92:46:4d:47 +# SHA256 Fingerprint: 34:9d:fa:40:58:c5:e2:63:12:3b:39:8a:e7:95:57:3c:4e:13:13:c8:3f:e6:8f:93:55:6c:d5:e8:03:1b:3c:7d +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi +QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR +HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D +9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8 +p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD +-----END CERTIFICATE----- diff --git a/certs/GlobalSign Atlas R3 DV TLS CA 2022 Q3.pem b/certs/GlobalSign Atlas R3 DV TLS CA 2022 Q3.pem deleted file mode 100644 index b514c11..0000000 --- a/certs/GlobalSign Atlas R3 DV TLS CA 2022 Q3.pem +++ /dev/null @@ -1,177 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 7c:2a:0c:21:3f:c6:55:53:45:c9:1f:19:1f:b8:4e:fa - Signature Algorithm: sha256WithRSAEncryption - Issuer: OU = GlobalSign Root CA - R3, O = GlobalSign, CN = GlobalSign - Validity - Not Before: Apr 20 12:00:00 2022 GMT - Not After : Apr 20 00:00:00 2025 GMT - Subject: C = BE, O = GlobalSign nv-sa, CN = GlobalSign Atlas R3 DV TLS CA 2022 Q3 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) - Modulus: - 00:b8:a8:7a:66:3c:4e:66:9c:ce:37:a5:54:35:4d: - 36:c7:99:d3:a8:27:36:f2:2f:c6:d5:18:3e:e9:09: - dd:05:d6:d7:2c:34:32:7c:08:63:49:d1:10:37:e5: - 78:5d:11:62:ce:6d:fb:2f:3f:37:94:db:8f:7b:30: - e9:5e:2c:d9:55:3f:b2:db:b9:a0:b5:60:37:8b:a4: - 06:32:35:50:a4:09:af:0a:45:ff:a8:1f:9b:65:8e: - dd:4a:e0:40:a1:e3:63:37:58:90:dd:75:3b:fc:0e: - 1c:82:40:98:bd:70:b1:c1:48:14:14:3c:04:4b:69: - dd:d4:9c:01:a6:e9:21:e3:82:0a:fe:e4:aa:bf:34: - a0:8c:cb:c9:79:6e:3e:5c:6a:52:9e:c4:ed:2b:c5: - 69:fe:50:3c:93:9d:b5:ff:2d:28:a8:6c:06:6c:9d: - c5:af:b2:59:fb:59:77:0d:74:7a:88:84:a4:d4:1d: - d4:ba:20:06:cc:b5:1e:48:4e:74:21:15:86:75:c0: - cc:5a:d1:05:cf:57:16:7a:13:17:ec:c2:4a:ae:d5: - 1e:72:aa:22:5a:8c:9c:82:32:c4:10:e6:42:6e:21: - 86:68:7c:80:23:30:35:d3:bd:b0:5e:0a:29:2b:f0: - 14:b1:18:37:d9:59:25:c3:e7:38:d9:e9:d4:2d:36: - 35:65 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Key Usage: critical - Digital Signature, Certificate Sign, CRL Sign - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication - X509v3 Basic Constraints: critical - CA:TRUE, pathlen:0 - X509v3 Subject Key Identifier: - FA:91:39:63:9A:FB:AD:10:24:E5:BE:B5:B9:DA:AB:D9:C4:46:69:AB - X509v3 Authority Key Identifier: - 8F:F0:4B:7F:A8:2E:45:24:AE:4D:50:FA:63:9A:8B:DE:E2:DD:1B:BC - Authority Information Access: - OCSP - URI:http://ocsp2.globalsign.com/rootr3 - CA Issuers - URI:http://secure.globalsign.com/cacert/root-r3.crt - X509v3 CRL Distribution Points: - Full Name: - URI:http://crl.globalsign.com/root-r3.crl - X509v3 Certificate Policies: - Policy: 2.23.140.1.2.1 - Policy: 1.3.6.1.4.1.4146.10.1.3 - Signature Algorithm: sha256WithRSAEncryption - Signature Value: - 14:33:2c:79:e5:3f:82:c6:70:3f:da:59:38:a7:bb:a2:76:ac: - 61:18:05:68:57:d9:0d:fb:8a:46:bc:f1:a8:e8:0c:70:02:1d: - c6:2f:97:ed:36:3e:9e:52:86:2f:5c:62:d8:d5:47:43:9a:73: - d1:2b:25:87:9f:44:b4:14:eb:26:bc:21:47:74:20:bd:9f:a4: - bf:b3:80:1d:4d:35:7d:cd:b9:b5:da:55:f2:90:50:c8:b2:17: - 4e:0e:b4:61:88:29:5f:44:5d:03:7f:57:91:81:d0:eb:30:ae: - d5:2a:ec:82:20:ce:4e:d2:b0:8b:95:02:61:73:d8:69:34:f4: - ad:63:0e:5c:e4:20:1f:a9:7d:ed:8e:e5:1c:04:bb:22:9f:c7: - a9:22:ca:99:3d:02:a7:67:e8:06:2d:fa:04:6b:bb:49:d2:6c: - 99:57:63:6c:2d:c2:61:78:e1:20:b1:fb:f6:bf:e1:82:39:39: - 3c:7b:ef:7d:1a:95:4a:b2:72:da:55:90:ae:ed:dd:e2:70:90: - 7c:1a:ee:b5:32:5a:5d:cf:d6:fa:45:f2:9e:01:0c:31:2f:89: - 84:fe:31:60:0f:fd:ee:a6:5b:84:d5:c7:18:e6:a4:f9:40:30: - 29:18:1e:fe:fc:41:b5:b9:29:05:75:8b:62:1a:5b:22:2e:bf: - e4:59:6c:b0 ------BEGIN CERTIFICATE----- -MIIEjzCCA3egAwIBAgIQfCoMIT/GVVNFyR8ZH7hO+jANBgkqhkiG9w0BAQsFADBM -MSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xv -YmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0yMjA0MjAxMjAwMDBaFw0y -NTA0MjAwMDAwMDBaMFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWdu -IG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAy -MDIyIFEzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKh6ZjxOZpzO -N6VUNU02x5nTqCc28i/G1Rg+6QndBdbXLDQyfAhjSdEQN+V4XRFizm37Lz83lNuP -ezDpXizZVT+y27mgtWA3i6QGMjVQpAmvCkX/qB+bZY7dSuBAoeNjN1iQ3XU7/A4c -gkCYvXCxwUgUFDwES2nd1JwBpukh44IK/uSqvzSgjMvJeW4+XGpSnsTtK8Vp/lA8 -k521/y0oqGwGbJ3Fr7JZ+1l3DXR6iISk1B3UuiAGzLUeSE50IRWGdcDMWtEFz1cW -ehMX7MJKrtUecqoiWoycgjLEEOZCbiGGaHyAIzA1072wXgopK/AUsRg32Vklw+c4 -2enULTY1ZQIDAQABo4IBXzCCAVswDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG -CCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW -BBT6kTljmvutECTlvrW52qvZxEZpqzAfBgNVHSMEGDAWgBSP8Et/qC5FJK5NUPpj -move4t0bvDB7BggrBgEFBQcBAQRvMG0wLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3Nw -Mi5nbG9iYWxzaWduLmNvbS9yb290cjMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9zZWN1 -cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L3Jvb3QtcjMuY3J0MDYGA1UdHwQvMC0w -K6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vcm9vdC1yMy5jcmwwIQYD -VR0gBBowGDAIBgZngQwBAgEwDAYKKwYBBAGgMgoBAzANBgkqhkiG9w0BAQsFAAOC -AQEAFDMseeU/gsZwP9pZOKe7onasYRgFaFfZDfuKRrzxqOgMcAIdxi+X7TY+nlKG -L1xi2NVHQ5pz0Sslh59EtBTrJrwhR3QgvZ+kv7OAHU01fc25tdpV8pBQyLIXTg60 -YYgpX0RdA39XkYHQ6zCu1SrsgiDOTtKwi5UCYXPYaTT0rWMOXOQgH6l97Y7lHAS7 -Ip/HqSLKmT0Cp2foBi36BGu7SdJsmVdjbC3CYXjhILH79r/hgjk5PHvvfRqVSrJy -2lWQru3d4nCQfBrutTJaXc/W+kXyngEMMS+JhP4xYA/97qZbhNXHGOak+UAwKRge -/vxBtbkpBXWLYhpbIi6/5FlssA== ------END CERTIFICATE----- -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 04:00:00:00:00:01:21:58:53:08:a2 - Signature Algorithm: sha256WithRSAEncryption - Issuer: OU = GlobalSign Root CA - R3, O = GlobalSign, CN = GlobalSign - Validity - Not Before: Mar 18 10:00:00 2009 GMT - Not After : Mar 18 10:00:00 2029 GMT - Subject: OU = GlobalSign Root CA - R3, O = GlobalSign, CN = GlobalSign - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) - Modulus: - 00:cc:25:76:90:79:06:78:22:16:f5:c0:83:b6:84: - ca:28:9e:fd:05:76:11:c5:ad:88:72:fc:46:02:43: - c7:b2:8a:9d:04:5f:24:cb:2e:4b:e1:60:82:46:e1: - 52:ab:0c:81:47:70:6c:dd:64:d1:eb:f5:2c:a3:0f: - 82:3d:0c:2b:ae:97:d7:b6:14:86:10:79:bb:3b:13: - 80:77:8c:08:e1:49:d2:6a:62:2f:1f:5e:fa:96:68: - df:89:27:95:38:9f:06:d7:3e:c9:cb:26:59:0d:73: - de:b0:c8:e9:26:0e:83:15:c6:ef:5b:8b:d2:04:60: - ca:49:a6:28:f6:69:3b:f6:cb:c8:28:91:e5:9d:8a: - 61:57:37:ac:74:14:dc:74:e0:3a:ee:72:2f:2e:9c: - fb:d0:bb:bf:f5:3d:00:e1:06:33:e8:82:2b:ae:53: - a6:3a:16:73:8c:dd:41:0e:20:3a:c0:b4:a7:a1:e9: - b2:4f:90:2e:32:60:e9:57:cb:b9:04:92:68:68:e5: - 38:26:60:75:b2:9f:77:ff:91:14:ef:ae:20:49:fc: - ad:40:15:48:d1:02:31:61:19:5e:b8:97:ef:ad:77: - b7:64:9a:7a:bf:5f:c1:13:ef:9b:62:fb:0d:6c:e0: - 54:69:16:a9:03:da:6e:e9:83:93:71:76:c6:69:85: - 82:17 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Subject Key Identifier: - 8F:F0:4B:7F:A8:2E:45:24:AE:4D:50:FA:63:9A:8B:DE:E2:DD:1B:BC - Signature Algorithm: sha256WithRSAEncryption - Signature Value: - 4b:40:db:c0:50:aa:fe:c8:0c:ef:f7:96:54:45:49:bb:96:00: - 09:41:ac:b3:13:86:86:28:07:33:ca:6b:e6:74:b9:ba:00:2d: - ae:a4:0a:d3:f5:f1:f1:0f:8a:bf:73:67:4a:83:c7:44:7b:78: - e0:af:6e:6c:6f:03:29:8e:33:39:45:c3:8e:e4:b9:57:6c:aa: - fc:12:96:ec:53:c6:2d:e4:24:6c:b9:94:63:fb:dc:53:68:67: - 56:3e:83:b8:cf:35:21:c3:c9:68:fe:ce:da:c2:53:aa:cc:90: - 8a:e9:f0:5d:46:8c:95:dd:7a:58:28:1a:2f:1d:de:cd:00:37: - 41:8f:ed:44:6d:d7:53:28:97:7e:f3:67:04:1e:15:d7:8a:96: - b4:d3:de:4c:27:a4:4c:1b:73:73:76:f4:17:99:c2:1f:7a:0e: - e3:2d:08:ad:0a:1c:2c:ff:3c:ab:55:0e:0f:91:7e:36:eb:c3: - 57:49:be:e1:2e:2d:7c:60:8b:c3:41:51:13:23:9d:ce:f7:32: - 6b:94:01:a8:99:e7:2c:33:1f:3a:3b:25:d2:86:40:ce:3b:2c: - 86:78:c9:61:2f:14:ba:ee:db:55:6f:df:84:ee:05:09:4d:bd: - 28:d8:72:ce:d3:62:50:65:1e:eb:92:97:83:31:d9:b3:b5:ca: - 47:58:3f:5f ------BEGIN CERTIFICATE----- -MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G -A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp -Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 -MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG -A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 -RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT -gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm -KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd -QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ -XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw -DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o -LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU -RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp -jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK -6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX -mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs -Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH -WD9f ------END CERTIFICATE----- diff --git a/certs/Go Daddy Secure Certificate Authority - G2.pem b/certs/Go Daddy Secure Certificate Authority - G2.pem deleted file mode 100644 index 4faba90..0000000 --- a/certs/Go Daddy Secure Certificate Authority - G2.pem +++ /dev/null @@ -1,178 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 7 (0x7) - Signature Algorithm: sha256WithRSAEncryption - Issuer: C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", CN = Go Daddy Root Certificate Authority - G2 - Validity - Not Before: May 3 07:00:00 2011 GMT - Not After : May 3 07:00:00 2031 GMT - Subject: C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", OU = http://certs.godaddy.com/repository/, CN = Go Daddy Secure Certificate Authority - G2 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:b9:e0:cb:10:d4:af:76:bd:d4:93:62:eb:30:64: - b8:81:08:6c:c3:04:d9:62:17:8e:2f:ff:3e:65:cf: - 8f:ce:62:e6:3c:52:1c:da:16:45:4b:55:ab:78:6b: - 63:83:62:90:ce:0f:69:6c:99:c8:1a:14:8b:4c:cc: - 45:33:ea:88:dc:9e:a3:af:2b:fe:80:61:9d:79:57: - c4:cf:2e:f4:3f:30:3c:5d:47:fc:9a:16:bc:c3:37: - 96:41:51:8e:11:4b:54:f8:28:be:d0:8c:be:f0:30: - 38:1e:f3:b0:26:f8:66:47:63:6d:de:71:26:47:8f: - 38:47:53:d1:46:1d:b4:e3:dc:00:ea:45:ac:bd:bc: - 71:d9:aa:6f:00:db:db:cd:30:3a:79:4f:5f:4c:47: - f8:1d:ef:5b:c2:c4:9d:60:3b:b1:b2:43:91:d8:a4: - 33:4e:ea:b3:d6:27:4f:ad:25:8a:a5:c6:f4:d5:d0: - a6:ae:74:05:64:57:88:b5:44:55:d4:2d:2a:3a:3e: - f8:b8:bd:e9:32:0a:02:94:64:c4:16:3a:50:f1:4a: - ae:e7:79:33:af:0c:20:07:7f:e8:df:04:39:c2:69: - 02:6c:63:52:fa:77:c1:1b:c8:74:87:c8:b9:93:18: - 50:54:35:4b:69:4e:bc:3b:d3:49:2e:1f:dc:c1:d2: - 52:fb - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - X509v3 Subject Key Identifier: - 40:C2:BD:27:8E:CC:34:83:30:A2:33:D7:FB:6C:B3:F0:B4:2C:80:CE - X509v3 Authority Key Identifier: - keyid:3A:9A:85:07:10:67:28:B6:EF:F6:BD:05:41:6E:20:C1:94:DA:0F:DE - - Authority Information Access: - OCSP - URI:http://ocsp.godaddy.com/ - - X509v3 CRL Distribution Points: - - Full Name: - URI:http://crl.godaddy.com/gdroot-g2.crl - - X509v3 Certificate Policies: - Policy: X509v3 Any Policy - CPS: https://certs.godaddy.com/repository/ - - Signature Algorithm: sha256WithRSAEncryption - 08:7e:6c:93:10:c8:38:b8:96:a9:90:4b:ff:a1:5f:4f:04:ef: - 6c:3e:9c:88:06:c9:50:8f:a6:73:f7:57:31:1b:be:bc:e4:2f: - db:f8:ba:d3:5b:e0:b4:e7:e6:79:62:0e:0c:a2:d7:6a:63:73: - 31:b5:f5:a8:48:a4:3b:08:2d:a2:5d:90:d7:b4:7c:25:4f:11: - 56:30:c4:b6:44:9d:7b:2c:9d:e5:5e:e6:ef:0c:61:aa:bf:e4: - 2a:1b:ee:84:9e:b8:83:7d:c1:43:ce:44:a7:13:70:0d:91:1f: - f4:c8:13:ad:83:60:d9:d8:72:a8:73:24:1e:b5:ac:22:0e:ca: - 17:89:62:58:44:1b:ab:89:25:01:00:0f:cd:c4:1b:62:db:51: - b4:d3:0f:51:2a:9b:f4:bc:73:fc:76:ce:36:a4:cd:d9:d8:2c: - ea:ae:9b:f5:2a:b2:90:d1:4d:75:18:8a:3f:8a:41:90:23:7d: - 5b:4b:fe:a4:03:58:9b:46:b2:c3:60:60:83:f8:7d:50:41:ce: - c2:a1:90:c3:bb:ef:02:2f:d2:15:54:ee:44:15:d9:0a:ae:a7: - 8a:33:ed:b1:2d:76:36:26:dc:04:eb:9f:f7:61:1f:15:dc:87: - 6f:ee:46:96:28:ad:a1:26:7d:0a:09:a7:2e:04:a3:8d:bc:f8: - bc:04:30:01 ------BEGIN CERTIFICATE----- -MIIE0DCCA7igAwIBAgIBBzANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT -EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp -ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTExMDUwMzA3MDAwMFoXDTMxMDUwMzA3 -MDAwMFowgbQxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH -EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjEtMCsGA1UE -CxMkaHR0cDovL2NlcnRzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvMTMwMQYDVQQD -EypHbyBEYWRkeSBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC54MsQ1K92vdSTYuswZLiBCGzD -BNliF44v/z5lz4/OYuY8UhzaFkVLVat4a2ODYpDOD2lsmcgaFItMzEUz6ojcnqOv -K/6AYZ15V8TPLvQ/MDxdR/yaFrzDN5ZBUY4RS1T4KL7QjL7wMDge87Am+GZHY23e -cSZHjzhHU9FGHbTj3ADqRay9vHHZqm8A29vNMDp5T19MR/gd71vCxJ1gO7GyQ5HY -pDNO6rPWJ0+tJYqlxvTV0KaudAVkV4i1RFXULSo6Pvi4vekyCgKUZMQWOlDxSq7n -eTOvDCAHf+jfBDnCaQJsY1L6d8EbyHSHyLmTGFBUNUtpTrw700kuH9zB0lL7AgMB -AAGjggEaMIIBFjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV -HQ4EFgQUQMK9J47MNIMwojPX+2yz8LQsgM4wHwYDVR0jBBgwFoAUOpqFBxBnKLbv -9r0FQW4gwZTaD94wNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8v -b2NzcC5nb2RhZGR5LmNvbS8wNQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2NybC5n -b2RhZGR5LmNvbS9nZHJvb3QtZzIuY3JsMEYGA1UdIAQ/MD0wOwYEVR0gADAzMDEG -CCsGAQUFBwIBFiVodHRwczovL2NlcnRzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkv -MA0GCSqGSIb3DQEBCwUAA4IBAQAIfmyTEMg4uJapkEv/oV9PBO9sPpyIBslQj6Zz -91cxG7685C/b+LrTW+C05+Z5Yg4MotdqY3MxtfWoSKQ7CC2iXZDXtHwlTxFWMMS2 -RJ17LJ3lXubvDGGqv+QqG+6EnriDfcFDzkSnE3ANkR/0yBOtg2DZ2HKocyQetawi -DsoXiWJYRBuriSUBAA/NxBti21G00w9RKpv0vHP8ds42pM3Z2Czqrpv1KrKQ0U11 -GIo/ikGQI31bS/6kA1ibRrLDYGCD+H1QQc7CoZDDu+8CL9IVVO5EFdkKrqeKM+2x -LXY2JtwE65/3YR8V3Idv7kaWKK2hJn0KCacuBKONvPi8BDAB ------END CERTIFICATE----- -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 0 (0x0) - Signature Algorithm: sha256WithRSAEncryption - Issuer: C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", CN = Go Daddy Root Certificate Authority - G2 - Validity - Not Before: Sep 1 00:00:00 2009 GMT - Not After : Dec 31 23:59:59 2037 GMT - Subject: C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", CN = Go Daddy Root Certificate Authority - G2 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:bf:71:62:08:f1:fa:59:34:f7:1b:c9:18:a3:f7: - 80:49:58:e9:22:83:13:a6:c5:20:43:01:3b:84:f1: - e6:85:49:9f:27:ea:f6:84:1b:4e:a0:b4:db:70:98: - c7:32:01:b1:05:3e:07:4e:ee:f4:fa:4f:2f:59:30: - 22:e7:ab:19:56:6b:e2:80:07:fc:f3:16:75:80:39: - 51:7b:e5:f9:35:b6:74:4e:a9:8d:82:13:e4:b6:3f: - a9:03:83:fa:a2:be:8a:15:6a:7f:de:0b:c3:b6:19: - 14:05:ca:ea:c3:a8:04:94:3b:46:7c:32:0d:f3:00: - 66:22:c8:8d:69:6d:36:8c:11:18:b7:d3:b2:1c:60: - b4:38:fa:02:8c:ce:d3:dd:46:07:de:0a:3e:eb:5d: - 7c:c8:7c:fb:b0:2b:53:a4:92:62:69:51:25:05:61: - 1a:44:81:8c:2c:a9:43:96:23:df:ac:3a:81:9a:0e: - 29:c5:1c:a9:e9:5d:1e:b6:9e:9e:30:0a:39:ce:f1: - 88:80:fb:4b:5d:cc:32:ec:85:62:43:25:34:02:56: - 27:01:91:b4:3b:70:2a:3f:6e:b1:e8:9c:88:01:7d: - 9f:d4:f9:db:53:6d:60:9d:bf:2c:e7:58:ab:b8:5f: - 46:fc:ce:c4:1b:03:3c:09:eb:49:31:5c:69:46:b3: - e0:47 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - X509v3 Subject Key Identifier: - 3A:9A:85:07:10:67:28:B6:EF:F6:BD:05:41:6E:20:C1:94:DA:0F:DE - Signature Algorithm: sha256WithRSAEncryption - 99:db:5d:79:d5:f9:97:59:67:03:61:f1:7e:3b:06:31:75:2d: - a1:20:8e:4f:65:87:b4:f7:a6:9c:bc:d8:e9:2f:d0:db:5a:ee: - cf:74:8c:73:b4:38:42:da:05:7b:f8:02:75:b8:fd:a5:b1:d7: - ae:f6:d7:de:13:cb:53:10:7e:8a:46:d1:97:fa:b7:2e:2b:11: - ab:90:b0:27:80:f9:e8:9f:5a:e9:37:9f:ab:e4:df:6c:b3:85: - 17:9d:3d:d9:24:4f:79:91:35:d6:5f:04:eb:80:83:ab:9a:02: - 2d:b5:10:f4:d8:90:c7:04:73:40:ed:72:25:a0:a9:9f:ec:9e: - ab:68:12:99:57:c6:8f:12:3a:09:a4:bd:44:fd:06:15:37:c1: - 9b:e4:32:a3:ed:38:e8:d8:64:f3:2c:7e:14:fc:02:ea:9f:cd: - ff:07:68:17:db:22:90:38:2d:7a:8d:d1:54:f1:69:e3:5f:33: - ca:7a:3d:7b:0a:e3:ca:7f:5f:39:e5:e2:75:ba:c5:76:18:33: - ce:2c:f0:2f:4c:ad:f7:b1:e7:ce:4f:a8:c4:9b:4a:54:06:c5: - 7f:7d:d5:08:0f:e2:1c:fe:7e:17:b8:ac:5e:f6:d4:16:b2:43: - 09:0c:4d:f6:a7:6b:b4:99:84:65:ca:7a:88:e2:e2:44:be:5c: - f7:ea:1c:f5 ------BEGIN CERTIFICATE----- -MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT -EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp -ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz -NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH -EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE -AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw -DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD -E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH -/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy -DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh -GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR -tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA -AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE -FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX -WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu -9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr -gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo -2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO -LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI -4uJEvlz36hz1 ------END CERTIFICATE----- diff --git a/certs/Go-Daddy-Root-Certificate-Authority-G2.pem b/certs/Go-Daddy-Root-Certificate-Authority-G2.pem new file mode 100644 index 0000000..c61f300 --- /dev/null +++ b/certs/Go-Daddy-Root-Certificate-Authority-G2.pem @@ -0,0 +1,30 @@ +# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Label: "Go Daddy Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 +# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b +# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- diff --git a/certs/ISRG-Root-X1.pem b/certs/ISRG-Root-X1.pem new file mode 100644 index 0000000..995c95d --- /dev/null +++ b/certs/ISRG-Root-X1.pem @@ -0,0 +1,38 @@ +# Issuer: CN=ISRG Root X1 O=Internet Security Research Group +# Subject: CN=ISRG Root X1 O=Internet Security Research Group +# Label: "ISRG Root X1" +# Serial: 172886928669790476064670243504169061120 +# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e +# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 +# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/certs/ISRG-Root-X2.pem b/certs/ISRG-Root-X2.pem new file mode 100644 index 0000000..9cca880 --- /dev/null +++ b/certs/ISRG-Root-X2.pem @@ -0,0 +1,21 @@ +# Issuer: CN=ISRG Root X2 O=Internet Security Research Group +# Subject: CN=ISRG Root X2 O=Internet Security Research Group +# Label: "ISRG Root X2" +# Serial: 87493402998870891108772069816698636114 +# MD5 Fingerprint: d3:9e:c4:1e:23:3c:a6:df:cf:a3:7e:6d:e0:14:e6:e5 +# SHA1 Fingerprint: bd:b1:b9:3c:d5:97:8d:45:c6:26:14:55:f8:db:95:c7:5a:d1:53:af +# SHA256 Fingerprint: 69:72:9b:8e:15:a8:6e:fc:17:7a:57:af:b7:17:1d:fc:64:ad:d2:8c:2f:ca:8c:f1:50:7e:34:45:3c:cb:14:70 +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- diff --git a/certs/Makefile b/certs/Makefile new file mode 100644 index 0000000..3ccad6e --- /dev/null +++ b/certs/Makefile @@ -0,0 +1,58 @@ +# 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/R3.pem b/certs/R3.pem deleted file mode 100644 index 837b709..0000000 --- a/certs/R3.pem +++ /dev/null @@ -1,237 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 91:2b:08:4a:cf:0c:18:a7:53:f6:d6:2e:25:a7:5f:5a - Signature Algorithm: sha256WithRSAEncryption - Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X1 - Validity - Not Before: Sep 4 00:00:00 2020 GMT - Not After : Sep 15 16:00:00 2025 GMT - Subject: C = US, O = Let's Encrypt, CN = R3 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:bb:02:15:28:cc:f6:a0:94:d3:0f:12:ec:8d:55: - 92:c3:f8:82:f1:99:a6:7a:42:88:a7:5d:26:aa:b5: - 2b:b9:c5:4c:b1:af:8e:6b:f9:75:c8:a3:d7:0f:47: - 94:14:55:35:57:8c:9e:a8:a2:39:19:f5:82:3c:42: - a9:4e:6e:f5:3b:c3:2e:db:8d:c0:b0:5c:f3:59:38: - e7:ed:cf:69:f0:5a:0b:1b:be:c0:94:24:25:87:fa: - 37:71:b3:13:e7:1c:ac:e1:9b:ef:db:e4:3b:45:52: - 45:96:a9:c1:53:ce:34:c8:52:ee:b5:ae:ed:8f:de: - 60:70:e2:a5:54:ab:b6:6d:0e:97:a5:40:34:6b:2b: - d3:bc:66:eb:66:34:7c:fa:6b:8b:8f:57:29:99:f8: - 30:17:5d:ba:72:6f:fb:81:c5:ad:d2:86:58:3d:17: - c7:e7:09:bb:f1:2b:f7:86:dc:c1:da:71:5d:d4:46: - e3:cc:ad:25:c1:88:bc:60:67:75:66:b3:f1:18:f7: - a2:5c:e6:53:ff:3a:88:b6:47:a5:ff:13:18:ea:98: - 09:77:3f:9d:53:f9:cf:01:e5:f5:a6:70:17:14:af: - 63:a4:ff:99:b3:93:9d:dc:53:a7:06:fe:48:85:1d: - a1:69:ae:25:75:bb:13:cc:52:03:f5:ed:51:a1:8b: - db:15 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Key Usage: critical - Digital Signature, Certificate Sign, CRL Sign - X509v3 Extended Key Usage: - TLS Web Client Authentication, TLS Web Server Authentication - X509v3 Basic Constraints: critical - CA:TRUE, pathlen:0 - X509v3 Subject Key Identifier: - 14:2E:B3:17:B7:58:56:CB:AE:50:09:40:E6:1F:AF:9D:8B:14:C2:C6 - X509v3 Authority Key Identifier: - keyid:79:B4:59:E6:7B:B6:E5:E4:01:73:80:08:88:C8:1A:58:F6:E9:9B:6E - - Authority Information Access: - CA Issuers - URI:http://x1.i.lencr.org/ - - X509v3 CRL Distribution Points: - - Full Name: - URI:http://x1.c.lencr.org/ - - X509v3 Certificate Policies: - Policy: 2.23.140.1.2.1 - Policy: 1.3.6.1.4.1.44947.1.1.1 - - Signature Algorithm: sha256WithRSAEncryption - 85:ca:4e:47:3e:a3:f7:85:44:85:bc:d5:67:78:b2:98:63:ad: - 75:4d:1e:96:3d:33:65:72:54:2d:81:a0:ea:c3:ed:f8:20:bf: - 5f:cc:b7:70:00:b7:6e:3b:f6:5e:94:de:e4:20:9f:a6:ef:8b: - b2:03:e7:a2:b5:16:3c:91:ce:b4:ed:39:02:e7:7c:25:8a:47: - e6:65:6e:3f:46:f4:d9:f0:ce:94:2b:ee:54:ce:12:bc:8c:27: - 4b:b8:c1:98:2f:a2:af:cd:71:91:4a:08:b7:c8:b8:23:7b:04: - 2d:08:f9:08:57:3e:83:d9:04:33:0a:47:21:78:09:82:27:c3: - 2a:c8:9b:b9:ce:5c:f2:64:c8:c0:be:79:c0:4f:8e:6d:44:0c: - 5e:92:bb:2e:f7:8b:10:e1:e8:1d:44:29:db:59:20:ed:63:b9: - 21:f8:12:26:94:93:57:a0:1d:65:04:c1:0a:22:ae:10:0d:43: - 97:a1:18:1f:7e:e0:e0:86:37:b5:5a:b1:bd:30:bf:87:6e:2b: - 2a:ff:21:4e:1b:05:c3:f5:18:97:f0:5e:ac:c3:a5:b8:6a:f0: - 2e:bc:3b:33:b9:ee:4b:de:cc:fc:e4:af:84:0b:86:3f:c0:55: - 43:36:f6:68:e1:36:17:6a:8e:99:d1:ff:a5:40:a7:34:b7:c0: - d0:63:39:35:39:75:6e:f2:ba:76:c8:93:02:e9:a9:4b:6c:17: - ce:0c:02:d9:bd:81:fb:9f:b7:68:d4:06:65:b3:82:3d:77:53: - f8:8e:79:03:ad:0a:31:07:75:2a:43:d8:55:97:72:c4:29:0e: - f7:c4:5d:4e:c8:ae:46:84:30:d7:f2:85:5f:18:a1:79:bb:e7: - 5e:70:8b:07:e1:86:93:c3:b9:8f:dc:61:71:25:2a:af:df:ed: - 25:50:52:68:8b:92:dc:e5:d6:b5:e3:da:7d:d0:87:6c:84:21: - 31:ae:82:f5:fb:b9:ab:c8:89:17:3d:e1:4c:e5:38:0e:f6:bd: - 2b:bd:96:81:14:eb:d5:db:3d:20:a7:7e:59:d3:e2:f8:58:f9: - 5b:b8:48:cd:fe:5c:4f:16:29:fe:1e:55:23:af:c8:11:b0:8d: - ea:7c:93:90:17:2f:fd:ac:a2:09:47:46:3f:f0:e9:b0:b7:ff: - 28:4d:68:32:d6:67:5e:1e:69:a3:93:b8:f5:9d:8b:2f:0b:d2: - 52:43:a6:6f:32:57:65:4d:32:81:df:38:53:85:5d:7e:5d:66: - 29:ea:b8:dd:e4:95:b5:cd:b5:56:12:42:cd:c4:4e:c6:25:38: - 44:50:6d:ec:ce:00:55:18:fe:e9:49:64:d4:4e:ca:97:9c:b4: - 5b:c0:73:a8:ab:b8:47:c2 ------BEGIN CERTIFICATE----- -MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw -WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg -RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP -R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx -sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm -NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg -Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG -/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC -AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB -Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA -FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw -AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw -Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB -gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W -PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl -ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz -CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm -lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4 -avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2 -yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O -yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids -hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+ -HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv -MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX -nLRbwHOoq7hHwg== ------END CERTIFICATE----- -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 82:10:cf:b0:d2:40:e3:59:44:63:e0:bb:63:82:8b:00 - Signature Algorithm: sha256WithRSAEncryption - Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X1 - Validity - Not Before: Jun 4 11:04:38 2015 GMT - Not After : Jun 4 11:04:38 2035 GMT - Subject: C = US, O = Internet Security Research Group, CN = ISRG Root X1 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (4096 bit) - Modulus: - 00:ad:e8:24:73:f4:14:37:f3:9b:9e:2b:57:28:1c: - 87:be:dc:b7:df:38:90:8c:6e:3c:e6:57:a0:78:f7: - 75:c2:a2:fe:f5:6a:6e:f6:00:4f:28:db:de:68:86: - 6c:44:93:b6:b1:63:fd:14:12:6b:bf:1f:d2:ea:31: - 9b:21:7e:d1:33:3c:ba:48:f5:dd:79:df:b3:b8:ff: - 12:f1:21:9a:4b:c1:8a:86:71:69:4a:66:66:6c:8f: - 7e:3c:70:bf:ad:29:22:06:f3:e4:c0:e6:80:ae:e2: - 4b:8f:b7:99:7e:94:03:9f:d3:47:97:7c:99:48:23: - 53:e8:38:ae:4f:0a:6f:83:2e:d1:49:57:8c:80:74: - b6:da:2f:d0:38:8d:7b:03:70:21:1b:75:f2:30:3c: - fa:8f:ae:dd:da:63:ab:eb:16:4f:c2:8e:11:4b:7e: - cf:0b:e8:ff:b5:77:2e:f4:b2:7b:4a:e0:4c:12:25: - 0c:70:8d:03:29:a0:e1:53:24:ec:13:d9:ee:19:bf: - 10:b3:4a:8c:3f:89:a3:61:51:de:ac:87:07:94:f4: - 63:71:ec:2e:e2:6f:5b:98:81:e1:89:5c:34:79:6c: - 76:ef:3b:90:62:79:e6:db:a4:9a:2f:26:c5:d0:10: - e1:0e:de:d9:10:8e:16:fb:b7:f7:a8:f7:c7:e5:02: - 07:98:8f:36:08:95:e7:e2:37:96:0d:36:75:9e:fb: - 0e:72:b1:1d:9b:bc:03:f9:49:05:d8:81:dd:05:b4: - 2a:d6:41:e9:ac:01:76:95:0a:0f:d8:df:d5:bd:12: - 1f:35:2f:28:17:6c:d2:98:c1:a8:09:64:77:6e:47: - 37:ba:ce:ac:59:5e:68:9d:7f:72:d6:89:c5:06:41: - 29:3e:59:3e:dd:26:f5:24:c9:11:a7:5a:a3:4c:40: - 1f:46:a1:99:b5:a7:3a:51:6e:86:3b:9e:7d:72:a7: - 12:05:78:59:ed:3e:51:78:15:0b:03:8f:8d:d0:2f: - 05:b2:3e:7b:4a:1c:4b:73:05:12:fc:c6:ea:e0:50: - 13:7c:43:93:74:b3:ca:74:e7:8e:1f:01:08:d0:30: - d4:5b:71:36:b4:07:ba:c1:30:30:5c:48:b7:82:3b: - 98:a6:7d:60:8a:a2:a3:29:82:cc:ba:bd:83:04:1b: - a2:83:03:41:a1:d6:05:f1:1b:c2:b6:f0:a8:7c:86: - 3b:46:a8:48:2a:88:dc:76:9a:76:bf:1f:6a:a5:3d: - 19:8f:eb:38:f3:64:de:c8:2b:0d:0a:28:ff:f7:db: - e2:15:42:d4:22:d0:27:5d:e1:79:fe:18:e7:70:88: - ad:4e:e6:d9:8b:3a:c6:dd:27:51:6e:ff:bc:64:f5: - 33:43:4f - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Subject Key Identifier: - 79:B4:59:E6:7B:B6:E5:E4:01:73:80:08:88:C8:1A:58:F6:E9:9B:6E - Signature Algorithm: sha256WithRSAEncryption - 55:1f:58:a9:bc:b2:a8:50:d0:0c:b1:d8:1a:69:20:27:29:08: - ac:61:75:5c:8a:6e:f8:82:e5:69:2f:d5:f6:56:4b:b9:b8:73: - 10:59:d3:21:97:7e:e7:4c:71:fb:b2:d2:60:ad:39:a8:0b:ea: - 17:21:56:85:f1:50:0e:59:eb:ce:e0:59:e9:ba:c9:15:ef:86: - 9d:8f:84:80:f6:e4:e9:91:90:dc:17:9b:62:1b:45:f0:66:95: - d2:7c:6f:c2:ea:3b:ef:1f:cf:cb:d6:ae:27:f1:a9:b0:c8:ae: - fd:7d:7e:9a:fa:22:04:eb:ff:d9:7f:ea:91:2b:22:b1:17:0e: - 8f:f2:8a:34:5b:58:d8:fc:01:c9:54:b9:b8:26:cc:8a:88:33: - 89:4c:2d:84:3c:82:df:ee:96:57:05:ba:2c:bb:f7:c4:b7:c7: - 4e:3b:82:be:31:c8:22:73:73:92:d1:c2:80:a4:39:39:10:33: - 23:82:4c:3c:9f:86:b2:55:98:1d:be:29:86:8c:22:9b:9e:e2: - 6b:3b:57:3a:82:70:4d:dc:09:c7:89:cb:0a:07:4d:6c:e8:5d: - 8e:c9:ef:ce:ab:c7:bb:b5:2b:4e:45:d6:4a:d0:26:cc:e5:72: - ca:08:6a:a5:95:e3:15:a1:f7:a4:ed:c9:2c:5f:a5:fb:ff:ac: - 28:02:2e:be:d7:7b:bb:e3:71:7b:90:16:d3:07:5e:46:53:7c: - 37:07:42:8c:d3:c4:96:9c:d5:99:b5:2a:e0:95:1a:80:48:ae: - 4c:39:07:ce:cc:47:a4:52:95:2b:ba:b8:fb:ad:d2:33:53:7d: - e5:1d:4d:6d:d5:a1:b1:c7:42:6f:e6:40:27:35:5c:a3:28:b7: - 07:8d:e7:8d:33:90:e7:23:9f:fb:50:9c:79:6c:46:d5:b4:15: - b3:96:6e:7e:9b:0c:96:3a:b8:52:2d:3f:d6:5b:e1:fb:08:c2: - 84:fe:24:a8:a3:89:da:ac:6a:e1:18:2a:b1:a8:43:61:5b:d3: - 1f:dc:3b:8d:76:f2:2d:e8:8d:75:df:17:33:6c:3d:53:fb:7b: - cb:41:5f:ff:dc:a2:d0:61:38:e1:96:b8:ac:5d:8b:37:d7:75: - d5:33:c0:99:11:ae:9d:41:c1:72:75:84:be:02:41:42:5f:67: - 24:48:94:d1:9b:27:be:07:3f:b9:b8:4f:81:74:51:e1:7a:b7: - ed:9d:23:e2:be:e0:d5:28:04:13:3c:31:03:9e:dd:7a:6c:8f: - c6:07:18:c6:7f:de:47:8e:3f:28:9e:04:06:cf:a5:54:34:77: - bd:ec:89:9b:e9:17:43:df:5b:db:5f:fe:8e:1e:57:a2:cd:40: - 9d:7e:62:22:da:de:18:27 ------BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- diff --git a/certs/Starfield Secure Certificate Authority - G2.pem b/certs/Starfield Secure Certificate Authority - G2.pem deleted file mode 100644 index 7772e6b..0000000 --- a/certs/Starfield Secure Certificate Authority - G2.pem +++ /dev/null @@ -1,179 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 7 (0x7) - Signature Algorithm: sha256WithRSAEncryption - Issuer: C = US, ST = Arizona, L = Scottsdale, O = "Starfield Technologies, Inc.", CN = Starfield Root Certificate Authority - G2 - Validity - Not Before: May 3 07:00:00 2011 GMT - Not After : May 3 07:00:00 2031 GMT - Subject: C = US, ST = Arizona, L = Scottsdale, O = "Starfield Technologies, Inc.", OU = http://certs.starfieldtech.com/repository/, CN = Starfield Secure Certificate Authority - G2 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:e5:90:66:4b:ec:f9:46:71:a9:20:83:be:e9:6c: - bf:4a:c9:48:69:81:75:4e:6d:24:f6:cb:17:13:f8: - b0:71:59:84:7a:6b:2b:85:a4:34:b5:16:e5:cb:cc: - e9:41:70:2c:a4:2e:d6:fa:32:7d:e1:a8:de:94:10: - ac:31:c1:c0:d8:6a:ff:59:27:ab:76:d6:fc:0b:74: - 6b:b8:a7:ae:3f:c4:54:f4:b4:31:44:dd:93:56:8c: - a4:4c:5e:9b:89:cb:24:83:9b:e2:57:7d:b7:d8:12: - 1f:c9:85:6d:f4:d1:80:f1:50:9b:87:ae:d4:0b:10: - 05:fb:27:ba:28:6d:17:e9:0e:d6:4d:b9:39:55:06: - ff:0a:24:05:7e:2f:c6:1d:72:6c:d4:8b:29:8c:57: - 7d:da:d9:eb:66:1a:d3:4f:a7:df:7f:52:c4:30:c5: - a5:c9:0e:02:c5:53:bf:77:38:68:06:24:c3:66:c8: - 37:7e:30:1e:45:71:23:35:ff:90:d8:2a:9d:8d:e7: - b0:92:4d:3c:7f:2a:0a:93:dc:cd:16:46:65:f7:60: - 84:8b:76:4b:91:27:73:14:92:e0:ea:ee:8f:16:ea: - 8d:0e:3e:76:17:bf:7d:89:80:80:44:43:e7:2d:e0: - 43:09:75:da:36:e8:ad:db:89:3a:f5:5d:12:8e:23: - 04:83 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - X509v3 Subject Key Identifier: - 25:45:81:68:50:26:38:3D:3B:2D:2C:BE:CD:6A:D9:B6:3D:B3:66:63 - X509v3 Authority Key Identifier: - keyid:7C:0C:32:1F:A7:D9:30:7F:C4:7D:68:A3:62:A8:A1:CE:AB:07:5B:27 - - Authority Information Access: - OCSP - URI:http://ocsp.starfieldtech.com/ - - X509v3 CRL Distribution Points: - - Full Name: - URI:http://crl.starfieldtech.com/sfroot-g2.crl - - X509v3 Certificate Policies: - Policy: X509v3 Any Policy - CPS: https://certs.starfieldtech.com/repository/ - - Signature Algorithm: sha256WithRSAEncryption - 56:65:ca:fe:f3:3f:0a:a8:93:8b:18:c7:de:43:69:13:34:20: - be:4e:5f:78:a8:6b:9c:db:6a:4d:41:db:c1:13:ec:dc:31:00: - 22:5e:f7:00:9e:0c:e0:34:65:34:f9:b1:3a:4e:48:c8:12:81: - 88:5c:5b:3e:08:53:7a:f7:1a:64:df:b8:50:61:cc:53:51:40: - 29:4b:c2:f4:ae:3a:5f:e4:ca:ad:26:cc:4e:61:43:e5:fd:57: - a6:37:70:ce:43:2b:b0:94:c3:92:e9:e1:5f:aa:10:49:b7:69: - e4:e0:d0:1f:64:a4:2b:cd:1f:6f:a0:f8:84:24:18:ce:79:3d: - a9:91:bf:54:18:13:89:99:54:11:0d:55:c5:26:0b:79:4f:5a: - 1c:6e:f9:63:db:14:80:a4:07:ab:fa:b2:a5:b9:88:dd:91:fe: - 65:3b:a4:a3:79:be:89:4d:e1:d0:b0:f4:c8:17:0c:0a:96:14: - 7c:09:b7:6c:e1:c2:d8:55:d4:18:a0:aa:41:69:70:24:a3:b9: - ef:e9:5a:dc:3e:eb:94:4a:f0:b7:de:5f:0e:76:fa:fb:fb:69: - 03:45:40:50:ee:72:0c:a4:12:86:81:cd:13:d1:4e:c4:3c:ca: - 4e:0d:d2:26:f1:00:b7:b4:a6:a2:e1:6e:7a:81:fd:30:ac:7a: - 1f:c7:59:7b ------BEGIN CERTIFICATE----- -MIIFADCCA+igAwIBAgIBBzANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT -HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs -ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTExMDUwMzA3MDAw -MFoXDTMxMDUwMzA3MDAwMFowgcYxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 -b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj -aG5vbG9naWVzLCBJbmMuMTMwMQYDVQQLEypodHRwOi8vY2VydHMuc3RhcmZpZWxk -dGVjaC5jb20vcmVwb3NpdG9yeS8xNDAyBgNVBAMTK1N0YXJmaWVsZCBTZWN1cmUg -Q2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQDlkGZL7PlGcakgg77pbL9KyUhpgXVObST2yxcT+LBxWYR6ayuF -pDS1FuXLzOlBcCykLtb6Mn3hqN6UEKwxwcDYav9ZJ6t21vwLdGu4p64/xFT0tDFE -3ZNWjKRMXpuJyySDm+JXfbfYEh/JhW300YDxUJuHrtQLEAX7J7oobRfpDtZNuTlV -Bv8KJAV+L8YdcmzUiymMV33a2etmGtNPp99/UsQwxaXJDgLFU793OGgGJMNmyDd+ -MB5FcSM1/5DYKp2N57CSTTx/KgqT3M0WRmX3YISLdkuRJ3MUkuDq7o8W6o0OPnYX -v32JgIBEQ+ct4EMJddo26K3biTr1XRKOIwSDAgMBAAGjggEsMIIBKDAPBgNVHRMB -Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUJUWBaFAmOD07LSy+ -zWrZtj2zZmMwHwYDVR0jBBgwFoAUfAwyH6fZMH/EfWijYqihzqsHWycwOgYIKwYB -BQUHAQEELjAsMCoGCCsGAQUFBzABhh5odHRwOi8vb2NzcC5zdGFyZmllbGR0ZWNo -LmNvbS8wOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovL2NybC5zdGFyZmllbGR0ZWNo -LmNvbS9zZnJvb3QtZzIuY3JsMEwGA1UdIARFMEMwQQYEVR0gADA5MDcGCCsGAQUF -BwIBFitodHRwczovL2NlcnRzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkv -MA0GCSqGSIb3DQEBCwUAA4IBAQBWZcr+8z8KqJOLGMfeQ2kTNCC+Tl94qGuc22pN -QdvBE+zcMQAiXvcAngzgNGU0+bE6TkjIEoGIXFs+CFN69xpk37hQYcxTUUApS8L0 -rjpf5MqtJsxOYUPl/VemN3DOQyuwlMOS6eFfqhBJt2nk4NAfZKQrzR9voPiEJBjO -eT2pkb9UGBOJmVQRDVXFJgt5T1ocbvlj2xSApAer+rKluYjdkf5lO6Sjeb6JTeHQ -sPTIFwwKlhR8Cbds4cLYVdQYoKpBaXAko7nv6VrcPuuUSvC33l8Odvr7+2kDRUBQ -7nIMpBKGgc0T0U7EPMpODdIm8QC3tKai4W56gf0wrHofx1l7 ------END CERTIFICATE----- -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 0 (0x0) - Signature Algorithm: sha256WithRSAEncryption - Issuer: C = US, ST = Arizona, L = Scottsdale, O = "Starfield Technologies, Inc.", CN = Starfield Root Certificate Authority - G2 - Validity - Not Before: Sep 1 00:00:00 2009 GMT - Not After : Dec 31 23:59:59 2037 GMT - Subject: C = US, ST = Arizona, L = Scottsdale, O = "Starfield Technologies, Inc.", CN = Starfield Root Certificate Authority - G2 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:bd:ed:c1:03:fc:f6:8f:fc:02:b1:6f:5b:9f:48: - d9:9d:79:e2:a2:b7:03:61:56:18:c3:47:b6:d7:ca: - 3d:35:2e:89:43:f7:a1:69:9b:de:8a:1a:fd:13:20: - 9c:b4:49:77:32:29:56:fd:b9:ec:8c:dd:22:fa:72: - dc:27:61:97:ee:f6:5a:84:ec:6e:19:b9:89:2c:dc: - 84:5b:d5:74:fb:6b:5f:c5:89:a5:10:52:89:46:55: - f4:b8:75:1c:e6:7f:e4:54:ae:4b:f8:55:72:57:02: - 19:f8:17:71:59:eb:1e:28:07:74:c5:9d:48:be:6c: - b4:f4:a4:b0:f3:64:37:79:92:c0:ec:46:5e:7f:e1: - 6d:53:4c:62:af:cd:1f:0b:63:bb:3a:9d:fb:fc:79: - 00:98:61:74:cf:26:82:40:63:f3:b2:72:6a:19:0d: - 99:ca:d4:0e:75:cc:37:fb:8b:89:c1:59:f1:62:7f: - 5f:b3:5f:65:30:f8:a7:b7:4d:76:5a:1e:76:5e:34: - c0:e8:96:56:99:8a:b3:f0:7f:a4:cd:bd:dc:32:31: - 7c:91:cf:e0:5f:11:f8:6b:aa:49:5c:d1:99:94:d1: - a2:e3:63:5b:09:76:b5:56:62:e1:4b:74:1d:96:d4: - 26:d4:08:04:59:d0:98:0e:0e:e6:de:fc:c3:ec:1f: - 90:f1 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - X509v3 Subject Key Identifier: - 7C:0C:32:1F:A7:D9:30:7F:C4:7D:68:A3:62:A8:A1:CE:AB:07:5B:27 - Signature Algorithm: sha256WithRSAEncryption - 11:59:fa:25:4f:03:6f:94:99:3b:9a:1f:82:85:39:d4:76:05: - 94:5e:e1:28:93:6d:62:5d:09:c2:a0:a8:d4:b0:75:38:f1:34: - 6a:9d:e4:9f:8a:86:26:51:e6:2c:d1:c6:2d:6e:95:20:4a:92: - 01:ec:b8:8a:67:7b:31:e2:67:2e:8c:95:03:26:2e:43:9d:4a: - 31:f6:0e:b5:0c:bb:b7:e2:37:7f:22:ba:00:a3:0e:7b:52:fb: - 6b:bb:3b:c4:d3:79:51:4e:cd:90:f4:67:07:19:c8:3c:46:7a: - 0d:01:7d:c5:58:e7:6d:e6:85:30:17:9a:24:c4:10:e0:04:f7: - e0:f2:7f:d4:aa:0a:ff:42:1d:37:ed:94:e5:64:59:12:20:77: - 38:d3:32:3e:38:81:75:96:73:fa:68:8f:b1:cb:ce:1f:c5:ec: - fa:9c:7e:cf:7e:b1:f1:07:2d:b6:fc:bf:ca:a4:bf:d0:97:05: - 4a:bc:ea:18:28:02:90:bd:54:78:09:21:71:d3:d1:7d:1d:d9: - 16:b0:a9:61:3d:d0:0a:00:22:fc:c7:7b:cb:09:64:45:0b:3b: - 40:81:f7:7d:7c:32:f5:98:ca:58:8e:7d:2a:ee:90:59:73:64: - f9:36:74:5e:25:a1:f5:66:05:2e:7f:39:15:a9:2a:fb:50:8b: - 8e:85:69:f4 ------BEGIN CERTIFICATE----- -MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT -HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs -ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw -MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 -b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj -aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp -Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg -nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 -HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N -Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN -dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 -HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G -CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU -sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 -4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg -8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K -pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 -mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 ------END CERTIFICATE----- diff --git a/certs/Starfield-Root-Certificate-Authority-G2.pem b/certs/Starfield-Root-Certificate-Authority-G2.pem new file mode 100644 index 0000000..4e6774d --- /dev/null +++ b/certs/Starfield-Root-Certificate-Authority-G2.pem @@ -0,0 +1,30 @@ +# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 +# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e +# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- diff --git a/certs/USERTrust-RSA-Certification-Authority.pem b/certs/USERTrust-RSA-Certification-Authority.pem new file mode 100644 index 0000000..0fbeef6 --- /dev/null +++ b/certs/USERTrust-RSA-Certification-Authority.pem @@ -0,0 +1,41 @@ +# 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.rsc b/check-certificates.rsc index 3410d4d..be8e4df 100644 --- a/check-certificates.rsc +++ b/check-certificates.rsc @@ -1,183 +1,242 @@ #!rsc by RouterOS # RouterOS script: check-certificates -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 +# requires device-mode, fetch # # check for certificate validity -# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-certificates.md +# https://rsc.eworm.de/doc/check-certificates.md -:local 0 "check-certificates"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global CertRenewTime; -:global CertRenewUrl; -:global CertWarnTime; -:global Identity; - -:global CertificateAvailable -:global EscapeForRegEx; -:global IfThenElse; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; -:global UrlEncode; -:global WaitFullyConnected; - -:local CheckCertificatesDownloadImport do={ - :local Name [ :tostr $1 ]; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; + :global CertRenewTime; :global CertRenewUrl; - :global CertRenewPass; + :global CertWarnTime; + :global Identity; - :global CertificateNameByCN; + :global CertificateAvailable :global EscapeForRegEx; - :global LogPrintExit2; + :global IfThenElse; + :global LogPrint; + :global ParseKeyValueStore; + :global ScriptLock; + :global SendNotification2; + :global SymbolForNotification; :global UrlEncode; - :global WaitForFile; - - :local Return false; + :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; - :foreach Type in={ ".pem"; ".p12" } do={ - :local CertFileName ([ $UrlEncode $Name ] . $Type); - :do { - /tool/fetch check-certificate=yes-without-crl \ - ($CertRenewUrl . $CertFileName) dst-path=$CertFileName as-value; - $WaitForFile $CertFileName; - - :local DecryptionFailed true; - :foreach PassPhrase in=$CertRenewPass do={ - :local Result [ /certificate/import file-name=$CertFileName passphrase=$PassPhrase as-value ]; - :if ($Result->"decryption-failures" = 0) do={ - :set DecryptionFailed false; + :if ($DecryptionFailed = true) do={ + $LogPrint warning $ScriptName ("Decryption failed for certificate file '" . $CertFileName . "'."); } - } - /file/remove [ find where name=$CertFileName ]; - :if ($DecryptionFailed = true) do={ - $LogPrintExit2 warning $0 ("Decryption failed for certificate file " . $CertFileName) false; - } + :foreach CertInChain in=[ /certificate/find where 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 ]; + } - :foreach CertInChain in=[ /certificate/find where name~("^" . [ $EscapeForRegEx $CertFileName ] . "_[0-9]+\$") \ - common-name!=$Name !(subject-alt-name~("(^|\\W)(DNS|IP):" . [ $EscapeForRegEx $Name ] . "(\\W|\$)")) !(common-name=[]) ] do={ - $CertificateNameByCN [ /certificate/get $CertInChain common-name ]; + :return true; + } on-error={ + $LogPrint debug $ScriptName ("Could not download certificate file '" . $CertFileName . "'."); } - - :set Return true; - } on-error={ - $LogPrintExit2 debug $0 ("Could not download certificate file " . $CertFileName) false; } + + :return false; } - :return $Return; -} + :local FormatInfo do={ + :local Cert $1; -:local FormatInfo do={ - :local CertVal $1; + :global FormatLine; + :global FormatMultiLines; + :global IfThenElse; - :global FormatLine; - :global FormatMultiLines; - :global IfThenElse; - :global ParseKeyValueStore; - - :local FormatExpire do={ - :global CharacterReplace; - :return [ $CharacterReplace [ $CharacterReplace [ :tostr $1 ] "w" "w " ] "d" "d " ]; - } + :local FormatExpire do={ + :global CharacterReplace; + :return [ $CharacterReplace [ $CharacterReplace [ :tostr $1 ] "w" "w " ] "d" "d " ]; + } - :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" . \ - [ $FormatLine "Issuer" ($CertVal->"ca" . ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN")) ] . "\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") ] ] ]); -} + :local FormatCertChain do={ + :local Cert $1; -$ScriptLock $0; -$WaitFullyConnected; + :global EitherOr; + :global ParseKeyValueStore; -:foreach Cert in=[ /certificate/find where !revoked !ca !scep-url expires-after<$CertRenewTime ] do={ - :local CertVal [ /certificate/get $Cert ]; - :local LastName; + :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; - - :local ImportSuccess false; - :set LastName ($CertVal->"common-name"); - :set ImportSuccess [ $CheckCertificatesDownloadImport $LastName ]; - :foreach SAN in=($CertVal->"subject-alt-name") do={ - :if ($ImportSuccess = false) do={ - :set LastName [ :pick $SAN ([ :find $SAN ":" ] + 1) [ :len $SAN ] ]; - :set ImportSuccess [ $CheckCertificatesDownloadImport $LastName ]; + :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 . "..."); } - :if ($CertVal->"fingerprint" != [ /certificate/get $Cert fingerprint ]) do={ - $LogPrintExit2 debug $0 ("Certificate '" . $CertVal->"name" . "' was updated in place.") false; - :set CertVal [ /certificate/get $Cert ]; - } else { - $LogPrintExit2 debug $0 ("Certificate '" . $CertVal->"name" . "' was not updated, but replaced.") false; + :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") ] ] ]); + } - :local CertNew [ /certificate/find where name~("^" . [ $EscapeForRegEx [ $UrlEncode $LastName ] ] . "\\.(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 ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } + $WaitFullyConnected; - :if ([ $CertificateAvailable ([ $ParseKeyValueStore ($CertNewVal->"issuer") ]->"CN") ] = false) do={ - $LogPrintExit2 warning $0 ("The certificate chain is not available!") false; - } + :foreach Cert in=[ /certificate/find where !revoked !ca !scep-url expires-after<$CertRenewTime ] do={ + :local CertVal [ /certificate/get $Cert ]; + :local LastName; + :local FetchName; - :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; + :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; } - /ip/service/set certificate=($CertNewVal->"name") [ find where certificate=($CertVal->"name") ]; + :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."); - /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") ]; + :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 ]; - /ip/hotspot/profile/set ssl-certificate=($CertNewVal->"name") [ find where ssl-certificate=($CertVal->"name") ]; + :if ([ $CertificateAvailable ([ $ParseKeyValueStore ($CertNewVal->"issuer") ]->"CN") ] = false) do={ + $LogPrint warning $ScriptName ("The certificate chain is not available!"); + } - /certificate/remove $Cert; - /certificate/set $CertNew name=($CertVal->"name"); - :set CertNewVal; - :set CertVal [ /certificate/get $CertNew ];; - } + :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=$0; silent=true; \ - subject=([ $SymbolForNotification "lock-with-ink-pen" ] . "Certificate renewed: " . ($CertVal->"name")); \ - message=("A certificate on " . $Identity . " has been renewed.\n\n" . [ $FormatInfo $CertVal ]) }); - $LogPrintExit2 info $0 ("The certificate " . ($CertVal->"name") . " has been renewed.") false; - } on-error={ - $LogPrintExit2 debug $0 ("Could not renew certificate " . ($CertVal->"name") . ".") false; + $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 ]; + :foreach Cert in=[ /certificate/find where !revoked !scep-url !(expires-after=[]) \ + expires-after<$CertWarnTime !(fingerprint=[]) ] do={ + :local CertVal [ /certificate/get $Cert ]; - :if ([ :len [ /certificate/scep-server/find where ca-cert=($CertVal->"ca") ] ] > 0) do={ - $LogPrintExit2 debug $0 ("Certificate \"" . ($CertVal->"name") . "\" is handled by SCEP, skipping.") false; - } else={ - :local State [ $IfThenElse (($CertVal->"expired") = true) "expired" "is about to expire" ]; + :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=$0; \ - subject=([ $SymbolForNotification "warning-sign" ] . "Certificate warning: " . ($CertVal->"name")); \ - message=("A certificate on " . $Identity . " " . $State . ".\n\n" . [ $FormatInfo $CertVal ]) }); - $LogPrintExit2 info $0 ("The certificate " . ($CertVal->"name") . " " . $State . \ - ", it is invalid after " . ($CertVal->"invalid-after") . ".") false; + $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.d/state.rsc b/check-health.d/state.rsc new file mode 100644 index 0000000..2991935 --- /dev/null +++ b/check-health.d/state.rsc @@ -0,0 +1,48 @@ +#!rsc by RouterOS +# RouterOS script: check-health.d/state +# Copyright (c) 2019-2025 Christian Hesse <mail@eworm.de> +# 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 new file mode 100644 index 0000000..a2f632d --- /dev/null +++ b/check-health.d/temperature.rsc @@ -0,0 +1,74 @@ +#!rsc by RouterOS +# RouterOS script: check-health.d/temperature +# Copyright (c) 2019-2025 Christian Hesse <mail@eworm.de> +# 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 new file mode 100644 index 0000000..9071c88 --- /dev/null +++ b/check-health.d/voltage.rsc @@ -0,0 +1,63 @@ +#!rsc by RouterOS +# RouterOS script: check-health.d/voltage +# Copyright (c) 2019-2025 Christian Hesse <mail@eworm.de> +# 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 index af0d70b..f02a249 100644 --- a/check-health.rsc +++ b/check-health.rsc @@ -1,168 +1,110 @@ #!rsc by RouterOS # RouterOS script: check-health -# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2019-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # check for RouterOS health state -# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-health.md +# https://rsc.eworm.de/doc/check-health.md -:local 0 "check-health"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global CheckHealthCPUUtilization; -:global CheckHealthCPUUtilizationNotified; -:global CheckHealthFreeRAMNotified; -:global CheckHealthLast; -:global CheckHealthTemperature; -:global CheckHealthTemperatureDeviation; -:global CheckHealthTemperatureNotified; -:global CheckHealthVoltageLow; -:global CheckHealthVoltagePercent; -:global Identity; - -:global FormatLine; -:global IfThenElse; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; - -:local TempToNum do={ - :global CharacterReplace; - :local T [ :toarray [ $CharacterReplace $1 "." "," ] ]; - :return ($T->0 * 10 + $T->1); -} - -$ScriptLock $0; - -:local Resource [ /system/resource/get ]; - -:set CheckHealthCPUUtilization (($CheckHealthCPUUtilization * 4 + ($Resource->"cpu-load") * 10) / 5); -:if ($CheckHealthCPUUtilization > 750 && $CheckHealthCPUUtilizationNotified != true) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "abacus,chart-increasing" ] . "Health warning: CPU utilization"); \ - message=("The average CPU utilization on " . $Identity . " is at " . ($CheckHealthCPUUtilization / 10) . "%!") }); - :set CheckHealthCPUUtilizationNotified true; -} -:if ($CheckHealthCPUUtilization < 650 && $CheckHealthCPUUtilizationNotified = true) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "abacus,chart-decreasing" ] . "Health recovery: CPU utilization"); \ - message=("The average CPU utilization on " . $Identity . " decreased to " . ($CheckHealthCPUUtilization / 10) . "%.") }); - :set CheckHealthCPUUtilizationNotified false; -} +:local 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); + } -:local CheckHealthFreeRAM ($Resource->"free-memory" * 100 / $Resource->"total-memory"); -:if ($CheckHealthFreeRAM < 20 && $CheckHealthFreeRAMNotified != true) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "card-file-box,chart-decreasing" ] . "Health warning: free RAM"); \ - message=("The available free RAM on " . $Identity . " is at " . $CheckHealthFreeRAM . "% (" . \ - ($Resource->"free-memory" / 1024 / 1024) . "MiB)!") }); - :set CheckHealthFreeRAMNotified true; -} -:if ($CheckHealthFreeRAM > 30 && $CheckHealthFreeRAMNotified = true) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "card-file-box,chart-increasing" ] . "Health recovery: free RAM"); \ - message=("The available free RAM on " . $Identity . " increased to " . $CheckHealthFreeRAM . "% (" . \ - ($Resource->"free-memory" / 1024 / 1024) . "MiB).") }); - :set CheckHealthFreeRAMNotified false; -} + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } -:if ([ :len [ /system/health/find ] ] = 0) do={ - $LogPrintExit2 debug $0 ("Your device does not provide any health values.") true; -} + :local Resource [ /system/resource/get ]; -:if ([ :typeof $CheckHealthLast ] != "array") do={ - :set CheckHealthLast ({}); -} -:if ([ :typeof $CheckHealthTemperatureNotified ] != "array") do={ - :set CheckHealthTemperatureNotified ({}); -} + :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; + } -:foreach Voltage in=[ /system/health/find where type="V" ] do={ - :local Name [ /system/health/get $Voltage name ]; - :local Value [ /system/health/get $Voltage value ]; + :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; + } - :if ([ :typeof ($CheckHealthLast->$Name) ] != "nothing") do={ - :local NumCurr [ $TempToNum $Value ]; - :local NumLast [ $TempToNum ($CheckHealthLast->$Name) ]; + :global CheckHealthPlugins ({}); + :if ([ :typeof $CheckHealthLast ] != "array") do={ + :set CheckHealthLast ({}); + } - :if ($NumLast * (100 + $CheckHealthVoltagePercent) < $NumCurr * 100 || \ - $NumLast * 100 > $NumCurr * (100 + $CheckHealthVoltagePercent)) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification ("high-voltage-sign,chart-" . [ $IfThenElse ($NumLast < \ - $NumCurr) "in" "de" ] . "creasing") ] . "Health warning: " . $Name); \ - message=("The " . $Name . " on " . $Identity . " jumped more than " . $CheckHealthVoltagePercent . "%.\n\n" . \ - [ $FormatLine "old value" ($CheckHealthLast->$Name . " V") ] . "\n" . \ - [ $FormatLine "new value" ($Value . " V") ]) }); - } else={ - :if ($NumCurr <= $CheckHealthVoltageLow && $NumLast > $CheckHealthVoltageLow) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "high-voltage-sign,chart-decreasing" ] . "Health warning: Low " . $Name); \ - message=("The " . $Name . " on " . $Identity . " dropped to " . $Value . " V below hard limit.") }); - } - :if ($NumCurr > $CheckHealthVoltageLow && $NumLast <= $CheckHealthVoltageLow) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "high-voltage-sign,chart-increasing" ] . "Health recovery: Low " . $Name); \ - message=("The " . $Name . " on " . $Identity . " recovered to " . $Value . " V above hard limit.") }); + :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."); } } - :set ($CheckHealthLast->$Name) $Value; -} -:foreach PSU in=[ /system/health/find where name~"^psu.*-state\$" ] do={ - :local Name [ /system/health/get $PSU name ]; - :local Value [ /system/health/get $PSU value ]; - - :if ([ :typeof ($CheckHealthLast->$Name) ] != "nothing") do={ - :if ($CheckHealthLast->$Name = "ok" && \ - $Value != "ok") do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "cross-mark" ] . "Health warning: " . $Name); \ - message=("The power supply unit '" . $Name . "' on " . $Identity . " failed!") }); - } - :if ($CheckHealthLast->$Name != "ok" && \ - $Value = "ok") do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Health recovery: " . $Name); \ - message=("The power supply unit '" . $Name . "' on " . $Identity . " recovered!") }); - } + :foreach PluginName,Discard in=$CheckHealthPlugins do={ + ($CheckHealthPlugins->$PluginName) \ + ("\$CheckHealthPlugins->\"" . $PluginName . "\""); } - :set ($CheckHealthLast->$Name) $Value; -} -:foreach Temperature in=[ /system/health/find where type="C" ] do={ - :local Name [ /system/health/get $Temperature name ]; - :local Value [ /system/health/get $Temperature value ]; - - :if ([ :typeof ($CheckHealthLast->$Name) ] != "nothing") do={ - :if ([ :typeof ($CheckHealthTemperature->$Name) ] != "num" ) do={ - $LogPrintExit2 info $0 ("No threshold given for " . $Name . ", assuming 50C.") false; - :set ($CheckHealthTemperature->$Name) 50; - } - :local Validate [ /system/health/get [ find where name=$Name ] value ]; - :while ($Value != $Validate) do={ - :set Value $Validate; - :set Validate [ /system/health/get [ find where name=$Name ] value ]; - } - :if ($Value > $CheckHealthTemperature->$Name && \ - $CheckHealthTemperatureNotified->$Name != true) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "fire" ] . "Health warning: " . $Name); \ - message=("The " . $Name . " on " . $Identity . " is above threshold: " . \ - $Value . "\C2\B0" . "C") }); - :set ($CheckHealthTemperatureNotified->$Name) true; - } - :if ($Value <= ($CheckHealthTemperature->$Name - $CheckHealthTemperatureDeviation) && \ - $CheckHealthTemperatureNotified->$Name = true) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Health recovery: " . $Name); \ - message=("The " . $Name . " on " . $Identity . " dropped below threshold: " . \ - $Value . "\C2\B0" . "C") }); - :set ($CheckHealthTemperatureNotified->$Name) false; - } - } - :set ($CheckHealthLast->$Name) $Value; + :set CheckHealthPlugins; +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/check-lte-firmware-upgrade.rsc b/check-lte-firmware-upgrade.rsc index a2aec01..c5b6cb5 100644 --- a/check-lte-firmware-upgrade.rsc +++ b/check-lte-firmware-upgrade.rsc @@ -1,88 +1,107 @@ #!rsc by RouterOS # RouterOS script: check-lte-firmware-upgrade -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # check for LTE firmware upgrade, send notification -# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-lte-firmware-upgrade.md +# https://rsc.eworm.de/doc/check-lte-firmware-upgrade.md -:local 0 "check-lte-firmware-upgrade"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global SentLteFirmwareUpgradeNotification; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:global ScriptLock; + :global SentLteFirmwareUpgradeNotification; -$ScriptLock $0; + :global ScriptLock; -:if ([ :typeof $SentLteFirmwareUpgradeNotification ] != "array") do={ - :global SentLteFirmwareUpgradeNotification ({}); -} + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } + + :if ([ :typeof $SentLteFirmwareUpgradeNotification ] != "array") do={ + :global SentLteFirmwareUpgradeNotification ({}); + } -:local CheckInterface do={ - :local Interface $1; + :local CheckInterface do={ + :local ScriptName $1; + :local Interface $2; - :global Identity; - :global SentLteFirmwareUpgradeNotification; + :global Identity; + :global SentLteFirmwareUpgradeNotification; - :global CharacterReplace; - :global FormatLine; - :global LogPrintExit2; - :global ScriptFromTerminal; - :global SendNotification2; - :global SymbolForNotification; - - :local IntName [ /interface/lte/get $Interface name ]; - :local Firmware; - :local Info; - :do { - :set Firmware [ /interface/lte/firmware-upgrade $Interface once as-value ]; - :set Info [ /interface/lte/monitor $Interface once as-value ]; - } on-error={ - $LogPrintExit2 debug $0 ("Could not get latest LTE firmware version for interface " . \ - $IntName . ".") false; - :return false; - } + :global FormatLine; + :global IfThenElse; + :global LogPrint; + :global ScriptFromTerminal; + :global SendNotification2; + :global SymbolForNotification; - :if (($Firmware->"installed") = ($Firmware->"latest")) do={ - :if ([ $ScriptFromTerminal $0 ] = true) do={ - $LogPrintExit2 info $0 ("No firmware upgrade available for LTE interface " . $IntName . ".") false; + :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; } - :return true; - } - :if ([ $ScriptFromTerminal $0 ] = true && \ - [ :len [ /system/script/find where name="unattended-lte-firmware-upgrade" ] ] > 0) do={ - :put ("Do you want to start unattended lte firmware upgrade for interface " . $IntName . "? [y/N]"); - :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={ - /system/script/run unattended-lte-firmware-upgrade; - $LogPrintExit2 info $0 ("Scheduled lte firmware upgrade for interface " . $IntName . "...") false; + :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; - } 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; - } + :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..."; + } + } - $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" . \ - [ $FormatLine "Interface" [ $CharacterReplace ($Info->"manufacturer" . " " . $Info->"model") ("\"") "" ] ] . "\n" . \ - "Firmware version:\n" . \ - [ $FormatLine " Installed" ($Firmware->"installed") ] . "\n" . \ - [ $FormatLine " Available" ($Firmware->"latest") ]); silent=true }); - :set ($SentLteFirmwareUpgradeNotification->$IntName) ($Firmware->"latest"); -} + :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 $Interface; + :foreach Interface in=[ /interface/lte/find ] do={ + $CheckInterface $ScriptName $Interface; + } +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/check-perpetual-license.rsc b/check-perpetual-license.rsc new file mode 100644 index 0000000..9a3a3c7 --- /dev/null +++ b/check-perpetual-license.rsc @@ -0,0 +1,78 @@ +#!rsc by RouterOS +# RouterOS script: check-perpetual-license +# Copyright (c) 2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 +# +# check perpetual license on CHR +# https://rsc.eworm.de/doc/check-perpetual-license.md + +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; + + :global Identity; + :global SentCertificateNotification; + + :global LogPrint; + :global ScriptLock; + :global SendNotification2; + :global SymbolForNotification; + :global WaitFullyConnected; + + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } + + $WaitFullyConnected; + + :local License [ /system/license/get ]; + :if ([ :typeof ($License->"deadline-at") ] != "str") do={ + $LogPrint info $ScriptName ("This device does not have a perpetual license."); + :set ExitOK true; + :error true; + } + + :if ([ :len ($License->"next-renewal-at") ] = 0 && ($License->"limited-upgrades") = true) do={ + $LogPrint warning $ScriptName ("Your license expired on " . ($License->"deadline-at") . "!"); + :if ($SentCertificateNotification != "expired") do={ + $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 SentCertificateNotification "expired"; + } + :set ExitOK true; + :error true; + } + + :if ([ :totime ($License->"deadline-at") ] - 3w < [ :timestamp ]) do={ + $LogPrint warning $ScriptName ("Your license will expire on " . ($License->"deadline-at") . "!"); + :if ($SentCertificateNotification != "warning") do={ + $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 . "...") }); + :set SentCertificateNotification "warning"; + } + :set ExitOK true; + :error true; + } + + :if ([ :typeof $SentCertificateNotification ] = "str" && \ + [ :totime ($License->"deadline-at") ] - 4w > [ :timestamp ]) do={ + $LogPrint info $ScriptName ("Your license was successfully renewed."); + $SendNotification2 ({ origin=$ScriptName; \ + subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "License renewed"); \ + message=("Your license was successfully renewed on " . $Identity . \ + ". It is now valid until " . ($License->"deadline-at") . ".") }); + :set SentCertificateNotification; + } +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; +} diff --git a/check-routeros-update.rsc b/check-routeros-update.rsc index d77164d..361be34 100644 --- a/check-routeros-update.rsc +++ b/check-routeros-update.rsc @@ -1,154 +1,218 @@ #!rsc by RouterOS # RouterOS script: check-routeros-update -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# 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://git.eworm.de/cgit/routeros-scripts/about/doc/check-routeros-update.md +# https://rsc.eworm.de/doc/check-routeros-update.md -:local 0 "check-routeros-update"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global Identity; -:global SafeUpdateAll; -:global SafeUpdateNeighbor; -:global SafeUpdateNeighborIdentity; -:global SafeUpdatePatch; -:global SafeUpdateUrl; -:global SentRouterosUpdateNotification; - -:global DeviceInfo; -:global EscapeForRegEx; -: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; +: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."); } - :error "Waiting for system to reboot."; -} -$ScriptLock $0; - -$WaitFullyConnected; + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } -:if ([ :len [ /system/scheduler/find where name="\$RebootForUpdate" ] ] > 0) do={ - :error "A reboot for update is already scheduled."; -} + :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; + } -$LogPrintExit2 debug $0 ("Checking for updates...") false; -/system/package/update/check-for-updates without-paging as-value; -:local Update [ /system/package/update/get ]; + $WaitFullyConnected; -:if ([ $ScriptFromTerminal $0 ] = true && ($Update->"installed-version") = ($Update->"latest-version")) do={ - $LogPrintExit2 info $0 ("System is already up to date.") true; -} + :if ([ :len [ /system/scheduler/find where name="_RebootForUpdate" ] ] > 0) do={ + :set ExitOK true; + :error "A reboot for update is already scheduled."; + } -:local NumInstalled [ $VersionToNum ($Update->"installed-version") ]; -:local NumLatest [ $VersionToNum ($Update->"latest-version") ]; -:local Link ("https://mikrotik.com/download/changelogs/" . $Update->"channel" . "-release-tree"); + $LogPrint debug $ScriptName ("Checking for updates..."); + /system/package/update/check-for-updates without-paging as-value; + :local Update [ /system/package/update/get ]; -:if ($NumLatest < 117505792) do={ - $LogPrintExit2 info $0 ("The version '" . ($Update->"latest-version") . "' is not a valid version.") true; -} + :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 ($NumInstalled < $NumLatest) do={ - :if ($SafeUpdateAll ~ "^YES,? ?PLEASE!?\$") do={ - $LogPrintExit2 info $0 ("Installing ALL versions automatically, including " . \ - $Update->"latest-version" . "...") false; - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update: " . $Update->"latest-version"); \ - message=("Installing ALL versions automatically, including " . $Update->"latest-version" . \ - "... Updating on " . $Identity . "..."); link=$Link; silent=true }); - $DoUpdate; + :if ([ :len ($Update->"latest-version") ] = 0) do={ + $LogPrint info $ScriptName ("Received an empty version string from server."); + :set ExitOK true; + :error false; } - :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: " . $Update->"latest-version"); \ - message=("Version " . $Update->"latest-version" . " is a patch update for " . $Update->"channel" . \ - ", updating on " . $Identity . "..."); link=$Link; silent=true }); - $DoUpdate; + :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 ($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 ]; - $LogPrintExit2 info $0 ("Seen a neighbor (" . $Neighbor . ") running version " . \ - $Update->"latest-version" . " from " . $Update->"channel" . ", updating...") false; - $SendNotification2 ({ origin=$0; \ + :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=("Seen a neighbor (" . $Neighbor . ") running version " . $Update->"latest-version" . \ - " from " . $Update->"channel" . ", updating on " . $Identity . "..."); link=$Link; silent=true }); - $DoUpdate; + message=("Installing ALL versions automatically, including " . $Update->"latest-version" . \ + "... 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") 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; \ + :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 considered safe for " . $Update->"channel" . \ + message=("Version " . $Update->"latest-version" . " is a patch update for " . $Update->"channel" . \ ", updating on " . $Identity . "..."); link=$Link; silent=true }); - $DoUpdate; + $DoUpdate $ScriptName; + :set ExitOK true; + :error true; } - } - :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 ($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 ($SentRouterosUpdateNotification = $Update->"latest-version") do={ - $LogPrintExit2 info $0 ("Already sent the RouterOS update notification for version " . \ - $Update->"latest-version" . ".") 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; + } + } - $SendNotification2 ({ origin=$0; \ - 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 ([ $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; + } -: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=$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"); } - $SendNotification2 ({ origin=$0; \ - 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 }); - $LogPrintExit2 info $0 ("A different RouterOS version " . ($Update->"latest-version") . \ - " is available for downgrade.") false; - :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.rsc b/collect-wireless-mac.capsman.rsc index f421a53..17e09e3 100644 --- a/collect-wireless-mac.capsman.rsc +++ b/collect-wireless-mac.capsman.rsc @@ -1,90 +1,100 @@ #!rsc by RouterOS # RouterOS script: collect-wireless-mac.capsman -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md # # provides: lease-script, order=40 +# requires RouterOS, version=7.15 # # collect wireless mac adresses in access list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/collect-wireless-mac.md +# https://rsc.eworm.de/doc/collect-wireless-mac.md # # !! 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; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:global EitherOr; -:global FormatLine; -:global FormatMultiLines; -:global GetMacVendor; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; + :global Identity; -$ScriptLock $0 false 10; + :global EitherOr; + :global FormatLine; + :global FormatMultiLines; + :global GetMacVendor; + :global LogPrint; + :global ScriptLock; + :global SendNotification2; + :global SymbolForNotification; -: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); + :if ([ $ScriptLock $ScriptName 10 ] = false) do={ + :set ExitOK true; + :error false; + } -: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 [ /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); - :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; + :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 $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 cname=($DnsName->0) ] do={ - :set DnsName ($DnsName, [ /ip/dns/static/get $CName name ]); + :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 ]) }); } - :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" . \ - [ $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."); } - } else={ - $LogPrintExit2 debug $0 ("No mac address available... Ignoring.") false; } +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/collect-wireless-mac.local.rsc b/collect-wireless-mac.local.rsc index 99f8d3b..4a38bfa 100644 --- a/collect-wireless-mac.local.rsc +++ b/collect-wireless-mac.local.rsc @@ -1,91 +1,101 @@ #!rsc by RouterOS # RouterOS script: collect-wireless-mac.local -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md # # provides: lease-script, order=40 +# requires RouterOS, version=7.15 # # collect wireless mac adresses in access list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/collect-wireless-mac.md +# https://rsc.eworm.de/doc/collect-wireless-mac.md # # !! 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; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:global EitherOr; -:global FormatLine; -:global FormatMultiLines; -:global GetMacVendor; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; + :global Identity; -$ScriptLock $0 false 10; + :global EitherOr; + :global FormatLine; + :global FormatMultiLines; + :global GetMacVendor; + :global LogPrint; + :global ScriptLock; + :global SendNotification2; + :global SymbolForNotification; -: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); + :if ([ $ScriptLock $ScriptName 10 ] = false) do={ + :set ExitOK true; + :error false; + } -: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 [ /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); - :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; + :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 $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 cname=($DnsName->0) ] do={ - :set DnsName ($DnsName, [ /ip/dns/static/get $CName name ]); + :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 ]) }); } - :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" . \ - [ $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."); } - } else={ - $LogPrintExit2 debug $0 ("No mac address available... Ignoring.") false; } +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/collect-wireless-mac.template.rsc b/collect-wireless-mac.template.rsc index a584c79..da901be 100644 --- a/collect-wireless-mac.template.rsc +++ b/collect-wireless-mac.template.rsc @@ -1,108 +1,118 @@ #!rsc by RouterOS # RouterOS script: collect-wireless-mac%TEMPL% -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md # # provides: lease-script, order=40 +# requires RouterOS, version=7.15 # # collect wireless mac adresses in access list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/collect-wireless-mac.md +# 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. -:local 0 "collect-wireless-mac%TEMPL%"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global Identity; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:global EitherOr; -:global FormatLine; -:global FormatMultiLines; -:global GetMacVendor; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; + :global Identity; -$ScriptLock $0 false 10; + :global EitherOr; + :global FormatLine; + :global FormatMultiLines; + :global GetMacVendor; + :global LogPrint; + :global ScriptLock; + :global SendNotification2; + :global SymbolForNotification; -:if ([ :len [ /caps-man/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={ -:if ([ :len [ /interface/wifiwave2/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/wifiwave2/access-list/add comment="--- collected above ---" disabled=yes; - /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 ([ /caps-man/access-list/find where comment="--- collected above ---" disabled ]->0); -:local PlaceBefore ([ /interface/wifiwave2/access-list/find where comment="--- collected above ---" disabled ]->0); -:local PlaceBefore ([ /interface/wireless/access-list/find where comment="--- collected above ---" disabled ]->0); + :if ([ $ScriptLock $ScriptName 10 ] = false) do={ + :set ExitOK true; + :error false; + } -:foreach Reg in=[ /caps-man/registration-table/find ] do={ -:foreach Reg in=[ /interface/wifiwave2/registration-table/find ] do={ -:foreach Reg in=[ /interface/wireless/registration-table/find ] do={ - :local RegVal; - :do { - :set RegVal [ /caps-man/registration-table/get $Reg ]; - :set RegVal [ /interface/wifiwave2/registration-table/get $Reg ]; - :set RegVal [ /interface/wireless/registration-table/get $Reg ]; - } on-error={ - $LogPrintExit2 debug $0 ("Device already gone... Ignoring.") 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); - :if ([ :len ($RegVal->"mac-address") ] > 0) do={ - :local AccessList ([ /caps-man/access-list/find where mac-address=($RegVal->"mac-address") ]->0); - :local AccessList ([ /interface/wifiwave2/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={ - $LogPrintExit2 debug $0 ("MAC address " . $RegVal->"mac-address" . " already known: " . \ - [ /caps-man/access-list/get $AccessList comment ]) false; - [ /interface/wifiwave2/access-list/get $AccessList comment ]) false; - [ /interface/wireless/access-list/get $AccessList comment ]) false; + :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 $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 cname=($DnsName->0) ] do={ - :set DnsName ($DnsName, [ /ip/dns/static/get $CName name ]); + :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 ]) }); } - :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; - /caps-man/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes; - /interface/wifiwave2/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=$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" . \ - [ $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."); } - } else={ - $LogPrintExit2 debug $0 ("No mac address available... Ignoring.") false; } +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/collect-wireless-mac.wifi.rsc b/collect-wireless-mac.wifi.rsc new file mode 100644 index 0000000..cb217ce --- /dev/null +++ b/collect-wireless-mac.wifi.rsc @@ -0,0 +1,100 @@ +#!rsc by RouterOS +# RouterOS script: collect-wireless-mac.wifi +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# 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/collect-wireless-mac.wifiwave2.rsc b/collect-wireless-mac.wifiwave2.rsc deleted file mode 100644 index 04e65e8..0000000 --- a/collect-wireless-mac.wifiwave2.rsc +++ /dev/null @@ -1,90 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: collect-wireless-mac.wifiwave2 -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md -# -# provides: lease-script, order=40 -# -# collect wireless mac adresses in access list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/collect-wireless-mac.md -# -# !! Do not edit this file, it is generated from template! - -:local 0 "collect-wireless-mac.wifiwave2"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global Identity; - -:global EitherOr; -:global FormatLine; -:global FormatMultiLines; -:global GetMacVendor; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; - -$ScriptLock $0 false 10; - -:if ([ :len [ /interface/wifiwave2/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={ - /interface/wifiwave2/access-list/add comment="--- collected above ---" disabled=yes; - $LogPrintExit2 warning $0 ("Added disabled access-list entry with comment '--- collected above ---'.") false; -} -:local PlaceBefore ([ /interface/wifiwave2/access-list/find where comment="--- collected above ---" disabled ]->0); - -:foreach Reg in=[ /interface/wifiwave2/registration-table/find ] do={ - :local RegVal; - :do { - :set RegVal [ /interface/wifiwave2/registration-table/get $Reg ]; - } on-error={ - $LogPrintExit2 debug $0 ("Device already gone... Ignoring.") false; - } - - :if ([ :len ($RegVal->"mac-address") ] > 0) do={ - :local AccessList ([ /interface/wifiwave2/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/wifiwave2/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 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 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"); - $LogPrintExit2 info $0 $Message false; - /interface/wifiwave2/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" . \ - [ $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={ - $LogPrintExit2 debug $0 ("No mac address available... Ignoring.") false; - } -} diff --git a/contrib/checksums.sh b/contrib/checksums.sh new file mode 100755 index 0000000..b472b49 --- /dev/null +++ b/contrib/checksums.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +# generate a checksums file as used by $ScriptInstallUpdate + +set -e + +md5sum $(find -name '*.rsc' | sort) | \ + sed -e "s| \./||" -e 's|.rsc$||' | \ + jq --raw-input --null-input '[ inputs | split (" ") | { (.[1]): (.[0]) }] | add' > 'checksums.json' diff --git a/daily-psk.capsman.rsc b/daily-psk.capsman.rsc index 0b5e666..5672931 100644 --- a/daily-psk.capsman.rsc +++ b/daily-psk.capsman.rsc @@ -1,90 +1,96 @@ #!rsc by RouterOS # RouterOS script: daily-psk.capsman -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> # Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # update daily PSK (pre shared key) -# https://git.eworm.de/cgit/routeros-scripts/about/doc/daily-psk.md +# https://rsc.eworm.de/doc/daily-psk.md # # !! Do not edit this file, it is generated from template! -:local 0 "daily-psk.capsman"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global DailyPskMatchComment; -:global DailyPskQrCodeUrl; -:global Identity; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:global FormatLine; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; -:global UrlEncode; -:global WaitForFile; -:global WaitFullyConnected; + :global DailyPskMatchComment; + :global DailyPskQrCodeUrl; + :global Identity; -$ScriptLock $0; -$WaitFullyConnected; + :global FormatLine; + :global LogPrint; + :global ScriptLock; + :global SendNotification2; + :global SymbolForNotification; + :global UrlEncode; + :global WaitForFile; + :global WaitFullyConnected; -# return pseudo-random string for PSK -:local GeneratePSK do={ - :local Date [ :tostr $1 ]; + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } + $WaitFullyConnected; - :global DailyPskSecrets; + # return pseudo-random string for PSK + :local GeneratePSK do={ + :local Date [ :tostr $1 ]; - :global ParseDate; + :global DailyPskSecrets; - :set Date [ $ParseDate $Date ]; + :global ParseDate; - :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)); + :set Date [ $ParseDate $Date ]; - :return (($DailyPskSecrets->0->(($Date->"day") - 1)) . \ - ($DailyPskSecrets->1->(($Date->"month") - 1)) . \ - ($DailyPskSecrets->2->$WeekDay)); -} + :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)); -:local Seen ({}); -:local Date [ /system/clock/get date ]; -:local NewPsk [ $GeneratePSK $Date ]; + :return (($DailyPskSecrets->0->(($Date->"day") - 1)) . \ + ($DailyPskSecrets->1->(($Date->"month") - 1)) . \ + ($DailyPskSecrets->2->$WeekDay)); + } -: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; + :local Seen ({}); + :local Date [ /system/clock/get date ]; + :local NewPsk [ $GeneratePSK $Date ]; - :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; + :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 ([ :len [ /caps-man/actual-interface-configuration/find where configuration.ssid=$Ssid !disabled ] ] > 0) do={ - :foreach SeenSsid in=$Seen do={ - :if ($SeenSsid = $Ssid) do={ - $LogPrintExit2 debug $0 ("Already sent a mail for SSID " . $Ssid . ", skipping.") false; - :set Skip 1; - } - } + :if ($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 ($Skip = 0) do={ - :set Seen ($Seen, $Ssid); - :local Link ($DailyPskQrCodeUrl . \ - "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]); - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid); \ - message=("This is the daily PSK on " . $Identity . ":\n\n" . \ - [ $FormatLine "SSID" $Ssid ] . "\n" . \ - [ $FormatLine "PSK" $NewPsk ] . "\n" . \ - [ $FormatLine "Date" $Date ] . "\n\n" . \ - "A client device specific rule must not exist!"); link=$Link }); + :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.rsc b/daily-psk.local.rsc index df6d962..9dea469 100644 --- a/daily-psk.local.rsc +++ b/daily-psk.local.rsc @@ -1,89 +1,95 @@ #!rsc by RouterOS # RouterOS script: daily-psk.local -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> # Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # update daily PSK (pre shared key) -# https://git.eworm.de/cgit/routeros-scripts/about/doc/daily-psk.md +# https://rsc.eworm.de/doc/daily-psk.md # # !! Do not edit this file, it is generated from template! -:local 0 "daily-psk.local"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global DailyPskMatchComment; -:global DailyPskQrCodeUrl; -:global Identity; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:global FormatLine; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; -:global UrlEncode; -:global WaitForFile; -:global WaitFullyConnected; + :global DailyPskMatchComment; + :global DailyPskQrCodeUrl; + :global Identity; -$ScriptLock $0; -$WaitFullyConnected; + :global FormatLine; + :global LogPrint; + :global ScriptLock; + :global SendNotification2; + :global SymbolForNotification; + :global UrlEncode; + :global WaitForFile; + :global WaitFullyConnected; -# return pseudo-random string for PSK -:local GeneratePSK do={ - :local Date [ :tostr $1 ]; + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } + $WaitFullyConnected; - :global DailyPskSecrets; + # return pseudo-random string for PSK + :local GeneratePSK do={ + :local Date [ :tostr $1 ]; - :global ParseDate; + :global DailyPskSecrets; - :set Date [ $ParseDate $Date ]; + :global ParseDate; - :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)); + :set Date [ $ParseDate $Date ]; - :return (($DailyPskSecrets->0->(($Date->"day") - 1)) . \ - ($DailyPskSecrets->1->(($Date->"month") - 1)) . \ - ($DailyPskSecrets->2->$WeekDay)); -} + :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)); -:local Seen ({}); -:local Date [ /system/clock/get date ]; -:local NewPsk [ $GeneratePSK $Date ]; + :return (($DailyPskSecrets->0->(($Date->"day") - 1)) . \ + ($DailyPskSecrets->1->(($Date->"month") - 1)) . \ + ($DailyPskSecrets->2->$WeekDay)); + } -: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; + :local Seen ({}); + :local Date [ /system/clock/get date ]; + :local NewPsk [ $GeneratePSK $Date ]; - :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; + :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 ([ :len [ /interface/wireless/find where name=$IntName !disabled ] ] = 1) do={ - :foreach SeenSsid in=$Seen do={ - :if ($SeenSsid = $Ssid) do={ - $LogPrintExit2 debug $0 ("Already sent a mail for SSID " . $Ssid . ", skipping.") false; - :set Skip 1; - } - } + :if ($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 ($Skip = 0) do={ - :set Seen ($Seen, $Ssid); - :local Link ($DailyPskQrCodeUrl . \ - "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]); - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid); \ - message=("This is the daily PSK on " . $Identity . ":\n\n" . \ - [ $FormatLine "SSID" $Ssid ] . "\n" . \ - [ $FormatLine "PSK" $NewPsk ] . "\n" . \ - [ $FormatLine "Date" $Date ] . "\n\n" . \ - "A client device specific rule must not exist!"); link=$Link }); + :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.rsc b/daily-psk.template.rsc index 125e6c5..8202eeb 100644 --- a/daily-psk.template.rsc +++ b/daily-psk.template.rsc @@ -1,105 +1,111 @@ #!rsc by RouterOS # RouterOS script: daily-psk%TEMPL% -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> # Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # update daily PSK (pre shared key) -# https://git.eworm.de/cgit/routeros-scripts/about/doc/daily-psk.md +# 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. -:local 0 "daily-psk%TEMPL%"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global DailyPskMatchComment; -:global DailyPskQrCodeUrl; -:global Identity; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:global FormatLine; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; -:global UrlEncode; -:global WaitForFile; -:global WaitFullyConnected; + :global DailyPskMatchComment; + :global DailyPskQrCodeUrl; + :global Identity; -$ScriptLock $0; -$WaitFullyConnected; + :global FormatLine; + :global LogPrint; + :global ScriptLock; + :global SendNotification2; + :global SymbolForNotification; + :global UrlEncode; + :global WaitForFile; + :global WaitFullyConnected; -# return pseudo-random string for PSK -:local GeneratePSK do={ - :local Date [ :tostr $1 ]; + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } + $WaitFullyConnected; - :global DailyPskSecrets; + # return pseudo-random string for PSK + :local GeneratePSK do={ + :local Date [ :tostr $1 ]; - :global ParseDate; + :global DailyPskSecrets; - :set Date [ $ParseDate $Date ]; + :global ParseDate; - :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)); + :set Date [ $ParseDate $Date ]; - :return (($DailyPskSecrets->0->(($Date->"day") - 1)) . \ - ($DailyPskSecrets->1->(($Date->"month") - 1)) . \ - ($DailyPskSecrets->2->$WeekDay)); -} + :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)); -:local Seen ({}); -:local Date [ /system/clock/get date ]; -:local NewPsk [ $GeneratePSK $Date ]; + :return (($DailyPskSecrets->0->(($Date->"day") - 1)) . \ + ($DailyPskSecrets->1->(($Date->"month") - 1)) . \ + ($DailyPskSecrets->2->$WeekDay)); + } -:foreach AccList in=[ /caps-man/access-list/find where comment~$DailyPskMatchComment ] do={ -:foreach AccList in=[ /interface/wifiwave2/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/wifiwave2/access-list/get $AccList ssid-regexp ]; - :local Configuration ([ /caps-man/configuration/find where ssid~$SsidRegExp ]->0); - :local Configuration ([ /interface/wifiwave2/configuration/find where ssid~$SsidRegExp ]->0); - :local Ssid [ /caps-man/configuration/get $Configuration ssid ]; - :local Ssid [ /interface/wifiwave2/configuration/get $Configuration ssid ]; - :local OldPsk [ /caps-man/access-list/get $AccList private-passphrase ]; - :local OldPsk [ /interface/wifiwave2/access-list/get $AccList passphrase ]; - # /caps-man /interface/wifiwave2 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; + :local Seen ({}); + :local Date [ /system/clock/get date ]; + :local NewPsk [ $GeneratePSK $Date ]; - :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; - /interface/wifiwave2/access-list/set $AccList passphrase=$NewPsk; - /interface/wireless/access-list/set $AccList private-pre-shared-key=$NewPsk; + :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 ([ :len [ /caps-man/actual-interface-configuration/find where configuration.ssid=$Ssid !disabled ] ] > 0) do={ - :if ([ :len [ /interface/wifiwave2/actual-configuration/find where configuration.ssid=$Ssid ] ] > 0) do={ - :if ([ :len [ /interface/wireless/find where name=$IntName !disabled ] ] = 1) do={ - :foreach SeenSsid in=$Seen do={ - :if ($SeenSsid = $Ssid) do={ - $LogPrintExit2 debug $0 ("Already sent a mail for SSID " . $Ssid . ", skipping.") false; - :set Skip 1; - } - } + :if ($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 ($Skip = 0) do={ - :set Seen ($Seen, $Ssid); - :local Link ($DailyPskQrCodeUrl . \ - "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]); - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid); \ - message=("This is the daily PSK on " . $Identity . ":\n\n" . \ - [ $FormatLine "SSID" $Ssid ] . "\n" . \ - [ $FormatLine "PSK" $NewPsk ] . "\n" . \ - [ $FormatLine "Date" $Date ] . "\n\n" . \ - "A client device specific rule must not exist!"); link=$Link }); + :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 new file mode 100644 index 0000000..3de3c5b --- /dev/null +++ b/daily-psk.wifi.rsc @@ -0,0 +1,96 @@ +#!rsc by RouterOS +# RouterOS script: daily-psk.wifi +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# Michael Gisbers <michael@gisbers.de> +# 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/daily-psk.wifiwave2.rsc b/daily-psk.wifiwave2.rsc deleted file mode 100644 index 40c79cd..0000000 --- a/daily-psk.wifiwave2.rsc +++ /dev/null @@ -1,90 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: daily-psk.wifiwave2 -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md -# -# update daily PSK (pre shared key) -# https://git.eworm.de/cgit/routeros-scripts/about/doc/daily-psk.md -# -# !! Do not edit this file, it is generated from template! - -:local 0 "daily-psk.wifiwave2"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global DailyPskMatchComment; -:global DailyPskQrCodeUrl; -:global Identity; - -:global FormatLine; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; -:global UrlEncode; -:global WaitForFile; -:global WaitFullyConnected; - -$ScriptLock $0; -$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/wifiwave2/access-list/find where comment~$DailyPskMatchComment ] do={ - :local SsidRegExp [ /interface/wifiwave2/access-list/get $AccList ssid-regexp ]; - :local Configuration ([ /interface/wifiwave2/configuration/find where ssid~$SsidRegExp ]->0); - :local Ssid [ /interface/wifiwave2/configuration/get $Configuration ssid ]; - :local OldPsk [ /interface/wifiwave2/access-list/get $AccList passphrase ]; - :local Skip 0; - - :if ($NewPsk != $OldPsk) do={ - $LogPrintExit2 info $0 ("Updating daily PSK for " . $Ssid . " to " . $NewPsk . " (was " . $OldPsk . ")") false; - /interface/wifiwave2/access-list/set $AccList passphrase=$NewPsk; - - :if ([ :len [ /interface/wifiwave2/actual-configuration/find where configuration.ssid=$Ssid ] ] > 0) do={ - :foreach SeenSsid in=$Seen do={ - :if ($SeenSsid = $Ssid) do={ - $LogPrintExit2 debug $0 ("Already sent a mail for SSID " . $Ssid . ", skipping.") false; - :set Skip 1; - } - } - - :if ($Skip = 0) do={ - :set Seen ($Seen, $Ssid); - :local Link ($DailyPskQrCodeUrl . \ - "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]); - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid); \ - message=("This is the daily PSK on " . $Identity . ":\n\n" . \ - [ $FormatLine "SSID" $Ssid ] . "\n" . \ - [ $FormatLine "PSK" $NewPsk ] . "\n" . \ - [ $FormatLine "Date" $Date ] . "\n\n" . \ - "A client device specific rule must not exist!"); link=$Link }); - } - } - } -} diff --git a/dhcp-lease-comment.capsman.rsc b/dhcp-lease-comment.capsman.rsc index 89d2c4e..36b31c8 100644 --- a/dhcp-lease-comment.capsman.rsc +++ b/dhcp-lease-comment.capsman.rsc @@ -1,33 +1,43 @@ #!rsc by RouterOS # RouterOS script: dhcp-lease-comment.capsman -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# 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://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-lease-comment.md +# https://rsc.eworm.de/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; -:global ScriptLock; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$ScriptLock $0; + :global LogPrint; + :global ScriptLock; -: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 ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; } - :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={ - $LogPrintExit2 info $0 ("Updating comment for DHCP lease " . $LeaseVal->"active-mac-address" . ": " . $NewComment) false; - /ip/dhcp-server/lease/set comment=$NewComment $Lease; + + :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.rsc b/dhcp-lease-comment.local.rsc index 11e92cc..35dc6f6 100644 --- a/dhcp-lease-comment.local.rsc +++ b/dhcp-lease-comment.local.rsc @@ -1,33 +1,43 @@ #!rsc by RouterOS # RouterOS script: dhcp-lease-comment.local -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# 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://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-lease-comment.md +# https://rsc.eworm.de/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; -:global ScriptLock; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$ScriptLock $0; + :global LogPrint; + :global ScriptLock; -: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 ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; } - :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={ - $LogPrintExit2 info $0 ("Updating comment for DHCP lease " . $LeaseVal->"active-mac-address" . ": " . $NewComment) false; - /ip/dhcp-server/lease/set comment=$NewComment $Lease; + + :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.rsc b/dhcp-lease-comment.template.rsc index 0f16285..47a8554 100644 --- a/dhcp-lease-comment.template.rsc +++ b/dhcp-lease-comment.template.rsc @@ -1,38 +1,48 @@ #!rsc by RouterOS # RouterOS script: dhcp-lease-comment%TEMPL% -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# 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://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-lease-comment.md +# 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. -:local 0 "dhcp-lease-comment%TEMPL%"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global LogPrintExit2; -:global ScriptLock; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$ScriptLock $0; + :global LogPrint; + :global ScriptLock; -: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/wifiwave2/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/wifiwave2/access-list/get $AccessList comment ]; - :set NewComment [ /interface/wireless/access-list/get $AccessList comment ]; + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; } - :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={ - $LogPrintExit2 info $0 ("Updating comment for DHCP lease " . $LeaseVal->"active-mac-address" . ": " . $NewComment) false; - /ip/dhcp-server/lease/set comment=$NewComment $Lease; + + :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 new file mode 100644 index 0000000..e0f9785 --- /dev/null +++ b/dhcp-lease-comment.wifi.rsc @@ -0,0 +1,43 @@ +#!rsc by RouterOS +# RouterOS script: dhcp-lease-comment.wifi +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# 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-lease-comment.wifiwave2.rsc b/dhcp-lease-comment.wifiwave2.rsc deleted file mode 100644 index 200c53b..0000000 --- a/dhcp-lease-comment.wifiwave2.rsc +++ /dev/null @@ -1,33 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: dhcp-lease-comment.wifiwave2 -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md -# -# provides: lease-script, order=60 -# -# update dhcp-server lease comment with infos from access-list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-lease-comment.md -# -# !! Do not edit this file, it is generated from template! - -:local 0 "dhcp-lease-comment.wifiwave2"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global LogPrintExit2; -:global ScriptLock; - -$ScriptLock $0; - -: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/wifiwave2/access-list/find where mac-address=($LeaseVal->"active-mac-address") ]->0); - :if ([ :len $AccessList ] > 0) do={ - :set NewComment [ /interface/wifiwave2/access-list/get $AccessList comment ]; - } - :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={ - $LogPrintExit2 info $0 ("Updating comment for DHCP lease " . $LeaseVal->"active-mac-address" . ": " . $NewComment) false; - /ip/dhcp-server/lease/set comment=$NewComment $Lease; - } -} diff --git a/dhcp-to-dns.rsc b/dhcp-to-dns.rsc index d6a39e5..9b94098 100644 --- a/dhcp-to-dns.rsc +++ b/dhcp-to-dns.rsc @@ -1,107 +1,130 @@ #!rsc by RouterOS # RouterOS script: dhcp-to-dns -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# 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://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-to-dns.md +# https://rsc.eworm.de/doc/dhcp-to-dns.md -:local 0 "dhcp-to-dns"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global Domain; -:global Identity; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:global CharacterReplace; -:global EitherOr; -:global IfThenElse; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; + :global Domain; + :global Identity; -$ScriptLock $0 false 10; + :global CleanName; + :global EitherOr; + :global IfThenElse; + :global LogPrint; + :global LogPrintOnce; + :global ParseKeyValueStore; + :global ScriptLock; -:local Ttl 5m; -:local CommentPrefix ("managed by " . $0 . " for "); -:local CommentString ("--- " . $0 . " above ---"); + :if ([ $ScriptLock $ScriptName 10 ] = false) do={ + :set ExitOK true; + :error false; + } -: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; - $LogPrintExit2 warning $0 ("Added disabled static dns record with name '" . $CommentString . "'.") false; -} -: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) (!type or type=A) ] do={ - :local DnsRecordVal [ /ip/dns/static/get $DnsRecord ]; - :local MacAddress [ $CharacterReplace ($DnsRecordVal->"comment") $CommentPrefix "" ]; - :if ([ :len [ /ip/dhcp-server/lease/find where active-mac-address=$MacAddress active-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; - /ip/dns/static/remove [ find where type=CNAME comment=($DnsRecordVal->"comment") ]; + :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 Lease in=[ /ip/dhcp-server/lease/find where status=bound ] do={ - :local LeaseVal; - :do { - :set LeaseVal [ /ip/dhcp-server/lease/get $Lease ]; - :local DupMacLeases [ /ip/dhcp-server/lease/find where active-mac-address=($LeaseVal->"active-mac-address") status=bound ]; - :if ([ :len $DupMacLeases ] > 1) do={ - $LogPrintExit2 debug $0 ("Multiple bound leases found for mac-address " . ($LeaseVal->"active-mac-address") . ", using last one.") false; - :set LeaseVal [ /ip/dhcp-server/lease/get ($DupMacLeases->([ :len $DupMacLeases ] - 1)) ]; + :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") ]; } - } on-error={ - $LogPrintExit2 debug $0 ("A lease just vanished, ignoring.") false; } - :if ([ :len ($LeaseVal->"active-address") ] > 0) do={ - :local Comment ($CommentPrefix . $LeaseVal->"active-mac-address"); - :local MacDash [ $CharacterReplace ($LeaseVal->"active-mac-address") ":" "-" ]; - :local HostName [ $CharacterReplace [ $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) ]; + :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."); } - :local NetworkInfo [ $ParseKeyValueStore ($NetworkVal->"comment") ]; - :local NetDomain ([ $IfThenElse ([ :len ($NetworkInfo->"name-extra") ] > 0) ($NetworkInfo->"name-extra" . ".") ] . \ - [ $EitherOr [ $EitherOr ($NetworkInfo->"domain") ($NetworkVal->"domain") ] $Domain ]); - :local DnsRecord [ /ip/dns/static/find where comment=$Comment (!type or type=A) ]; - :if ([ :len $DnsRecord ] > 0) do={ - :local DnsRecordVal [ /ip/dns/static/get $DnsRecord ]; + :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; + } - :if ($DnsRecordVal->"address" = $LeaseVal->"active-address" && $DnsRecordVal->"name" = ($MacDash . "." . $NetDomain)) do={ - $LogPrintExit2 debug $0 ("DNS entry for " . $LeaseVal->"active-mac-address" . " does not need updating.") false; } else={ - $LogPrintExit2 info $0 ("Replacing DNS entry for " . $LeaseVal->"active-mac-address" . " (" . ($MacDash . "." . $NetDomain) . " -> " . $LeaseVal->"active-address" . ").") false; - /ip/dns/static/set address=($LeaseVal->"active-address") name=($MacDash . "." . $NetDomain) $DnsRecord; + $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; + } } - :local Cname [ /ip/dns/static/find where comment=$Comment type=CNAME ]; - :if ([ :len $Cname ] = 0 && [ :len $HostName ] > 0) do={ - $LogPrintExit2 info $0 ("Host name appeared, adding CNAME (" . ($HostName . "." . $NetDomain) . " -> " . ($MacDash . "." . $NetDomain) . ").") false; - /ip/dns/static/add name=($HostName . "." . $NetDomain) type=CNAME cname=($MacDash . "." . $NetDomain) ttl=$Ttl comment=$Comment place-before=$PlaceBefore; - } - :if ([ :len $Cname ] > 0 && [ /ip/dns/static/get $Cname name ] != ($HostName . "." . $NetDomain)) do={ - $LogPrintExit2 info $0 ("Host name or domain changed, updating CNAME (" . ($HostName . "." . $NetDomain) . " -> " . ($MacDash . "." . $NetDomain) . ").") false; - /ip/dns/static/set name=($HostName . "." . $NetDomain) cname=($MacDash . "." . $NetDomain) $Cname; + :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={ - $LogPrintExit2 info $0 ("Adding new DNS entry for " . $LeaseVal->"active-mac-address" . " (" . ($MacDash . "." . $NetDomain) . " -> " . $LeaseVal->"active-address" . ").") false; - /ip/dns/static/add name=($MacDash . "." . $NetDomain) type=A address=($LeaseVal->"active-address") ttl=$Ttl comment=$Comment place-before=$PlaceBefore; - :if ([ :len $HostName ] > 0) do={ - $LogPrintExit2 info $0 ("Adding new CNAME (" . ($HostName . "." . $NetDomain) . " -> " . ($MacDash . "." . $NetDomain) . ").") false; - /ip/dns/static/add name=($HostName . "." . $NetDomain) type=CNAME cname=($MacDash . "." . $NetDomain) ttl=$Ttl comment=$Comment place-before=$PlaceBefore; - } + $LogPrint debug $ScriptName ("No address available... Ignoring."); } - } else={ - $LogPrintExit2 debug $0 ("No address available... Ignoring.") false; } +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/doc/accesslist-duplicates.md b/doc/accesslist-duplicates.md index e3767e7..e4d0c7f 100644 --- a/doc/accesslist-duplicates.md +++ b/doc/accesslist-duplicates.md @@ -1,6 +1,13 @@ Find and remove access list duplicates ====================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -15,13 +22,13 @@ entries in wireless access list. Requirements and installation ----------------------------- -Depending on whether you use `wifiwave2` package (`/interface/wifiwave2`) -or legacy wifi with CAPsMAN (`/caps-man`) or local wireless interface +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. -For `wifiwave2`: +For `wifi`: - $ScriptInstallUpdate accesslist-duplicates.wifiwave2; + $ScriptInstallUpdate accesslist-duplicates.wifi; For legacy CAPsMAN: @@ -36,7 +43,7 @@ Usage and invocation Run this script from a terminal: - /system/script/run accesslist-duplicates.local; + /system/script/run accesslist-duplicates.wifi;  diff --git a/doc/backup-cloud.md b/doc/backup-cloud.md index b612fc4..7d55d74 100644 --- a/doc/backup-cloud.md +++ b/doc/backup-cloud.md @@ -1,6 +1,13 @@ Upload backup to Mikrotik cloud =============================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -10,12 +17,12 @@ Description ----------- This script uploads -[binary backup to Mikrotik cloud](https://wiki.mikrotik.com/wiki/Manual:IP/Cloud#Backup). +[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 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. +> ⚠️ **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. ### Sample notification @@ -42,7 +49,9 @@ The configuration goes to `global-config-overlay`, these are the parameters: Also notification settings are required for [e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md) and/or +[gotify](mod/notification-gotify.md), +[matrix](mod/notification-matrix.md), +[ntfy](mod/notification-ntfy.md) and/or [telegram](mod/notification-telegram.md). Usage and invocation @@ -60,7 +69,7 @@ See also -------- * [Send backup via e-mail](backup-email.md) -* [Save configuration to fallback partition](doc/backup-partition.md) +* [Save configuration to fallback partition](backup-partition.md) * [Upload backup to server](backup-upload.md) --- diff --git a/doc/backup-email.md b/doc/backup-email.md index 67564e7..7b8bcfe 100644 --- a/doc/backup-email.md +++ b/doc/backup-email.md @@ -1,6 +1,13 @@ Send backup via e-mail ====================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -52,7 +59,7 @@ See also -------- * [Upload backup to Mikrotik cloud](backup-cloud.md) -* [Save configuration to fallback partition](doc/backup-partition.md) +* [Save configuration to fallback partition](backup-partition.md) * [Send notifications via e-mail](mod/notification-email.md) * [Upload backup to server](backup-upload.md) diff --git a/doc/backup-partition.md b/doc/backup-partition.md index e95422c..50b8a09 100644 --- a/doc/backup-partition.md +++ b/doc/backup-partition.md @@ -1,6 +1,13 @@ Save configuration to fallback partition ======================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -10,11 +17,21 @@ Description ----------- This script saves the current configuration to fallback -[partition](https://wiki.mikrotik.com/wiki/Manual:Partitions). +[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 ----------------------------- @@ -22,6 +39,18 @@ 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 -------------------- @@ -29,6 +58,9 @@ 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. + 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; @@ -39,6 +71,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) diff --git a/doc/backup-upload.md b/doc/backup-upload.md index f9aaa29..b4012c8 100644 --- a/doc/backup-upload.md +++ b/doc/backup-upload.md @@ -1,6 +1,13 @@ Upload backup to server ======================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -48,7 +55,9 @@ The configuration goes to `global-config-overlay`, these are the parameters: Also notification settings are required for [e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md) and/or +[gotify](mod/notification-gotify.md), +[matrix](mod/notification-matrix.md), +[ntfy](mod/notification-ntfy.md) and/or [telegram](mod/notification-telegram.md). ### Issues with SFTP client @@ -77,7 +86,7 @@ See also * [Upload backup to Mikrotik cloud](backup-cloud.md) * [Send backup via e-mail](backup-email.md) -* [Save configuration to fallback partition](doc/backup-partition.md) +* [Save configuration to fallback partition](backup-partition.md) --- [⬅️ Go back to main README](../README.md) diff --git a/doc/capsman-download-packages.md b/doc/capsman-download-packages.md index 8174c13..5722227 100644 --- a/doc/capsman-download-packages.md +++ b/doc/capsman-download-packages.md @@ -1,6 +1,13 @@ Download packages for CAP upgrade from CAPsMAN ============================================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -18,21 +25,25 @@ This script automatically downloads these packages. Requirements and installation ----------------------------- -Just install the script on CAPsMAN device. Depending on whether you use -`wifiwave2` package (`/interface/wifiwave2`) or legacy wifi with CAPsMAN -(`/caps-man`) you need to install a different script. +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. + +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. -For `wifiwave2`: +For `wifi`: - $ScriptInstallUpdate capsman-download-packages.wifiwave2; + $ScriptInstallUpdate capsman-download-packages.wifi; For legacy CAPsMAN: $ScriptInstallUpdate capsman-download-packages.capsman; -Optionally add a scheduler to run after startup. For `wifiwave2`: +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.wifiwave2;" start-time=startup; + /system/scheduler/add name=capsman-download-packages on-event="/system/script/run capsman-download-packages.wifi;" start-time=startup; For legacy CAPsMAN: @@ -41,16 +52,20 @@ For legacy CAPsMAN: Packages available in local storage in older version are downloaded unconditionally. -If no packages are found the script tries to download missing packages for -legacy CAPsMAN by guessing from system log. For `wifiwave2` a default set -of packages (`routeros` and `wifiwave2` for *arm* and *arm64*) is downloaded. +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. Usage and invocation -------------------- Run the script manually: - /system/script/run capsman-download-packages.wifiwave2; + /system/script/run capsman-download-packages.wifi; ... or from scheduler. diff --git a/doc/capsman-rolling-upgrade.md b/doc/capsman-rolling-upgrade.md index 5c038e9..d277db6 100644 --- a/doc/capsman-rolling-upgrade.md +++ b/doc/capsman-rolling-upgrade.md @@ -1,6 +1,13 @@ Run rolling CAP upgrades from CAPsMAN ===================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -20,13 +27,13 @@ parallel. Requirements and installation ----------------------------- -Just install the script on CAPsMAN device. Depending on whether you use -`wifiwave2` package (`/interface/wifiwave2`) or legacy wifi with CAPsMAN -(`/caps-man`) you need to install a different script. +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. -For `wifiwave2`: +For `wifi`: - $ScriptInstallUpdate capsman-rolling-upgrade.wifiwave2; + $ScriptInstallUpdate capsman-rolling-upgrade.wifi; For legacy CAPsMAN: @@ -41,7 +48,7 @@ that script when required. Alternatively run it manually: - /system/script/run capsman-rolling-upgrade.wifiwave2; + /system/script/run capsman-rolling-upgrade.wifi; See also -------- diff --git a/doc/certificate-renew-issued.md b/doc/certificate-renew-issued.md index bb8e18c..c4615b5 100644 --- a/doc/certificate-renew-issued.md +++ b/doc/certificate-renew-issued.md @@ -1,6 +1,13 @@ Renew locally issued certificates ================================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/check-certificates.md b/doc/check-certificates.md index 58151f3..a9426db 100644 --- a/doc/check-certificates.md +++ b/doc/check-certificates.md @@ -1,6 +1,13 @@ Renew certificates and notify on expiration =========================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -44,7 +51,9 @@ 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) and/or +[gotify](mod/notification-gotify.md), +[matrix](mod/notification-matrix.md), +[ntfy](mod/notification-ntfy.md) and/or [telegram](mod/notification-telegram.md). Usage and invocation @@ -62,11 +71,23 @@ Just run the script: 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; + See also -------- diff --git a/doc/check-health.d/notification-03-free-ram-low.avif b/doc/check-health.d/notification-03-free-ram-low.avif Binary files differdeleted file mode 100644 index effc87c..0000000 --- a/doc/check-health.d/notification-03-free-ram-low.avif +++ /dev/null diff --git a/doc/check-health.d/notification-03-ram-utilization-high.avif b/doc/check-health.d/notification-03-ram-utilization-high.avif Binary files differnew file mode 100644 index 0000000..59155c5 --- /dev/null +++ b/doc/check-health.d/notification-03-ram-utilization-high.avif diff --git a/doc/check-health.d/notification-04-free-ram-ok.avif b/doc/check-health.d/notification-04-free-ram-ok.avif Binary files differdeleted file mode 100644 index cadb1d0..0000000 --- a/doc/check-health.d/notification-04-free-ram-ok.avif +++ /dev/null diff --git a/doc/check-health.d/notification-04-ram-utilization-ok.avif b/doc/check-health.d/notification-04-ram-utilization-ok.avif Binary files differnew file mode 100644 index 0000000..d995b9a --- /dev/null +++ b/doc/check-health.d/notification-04-ram-utilization-ok.avif diff --git a/doc/check-health.d/notification-08-psu-fail.avif b/doc/check-health.d/notification-08-state-fail.avif Binary files differindex ad049ac..ad049ac 100644 --- a/doc/check-health.d/notification-08-psu-fail.avif +++ b/doc/check-health.d/notification-08-state-fail.avif diff --git a/doc/check-health.d/notification-09-psu-ok.avif b/doc/check-health.d/notification-09-state-ok.avif Binary files differindex 26f5a74..26f5a74 100644 --- a/doc/check-health.d/notification-09-psu-ok.avif +++ b/doc/check-health.d/notification-09-state-ok.avif diff --git a/doc/check-health.md b/doc/check-health.md index 3f4c70f..33847e3 100644 --- a/doc/check-health.md +++ b/doc/check-health.md @@ -1,6 +1,13 @@ Notify about health state ========================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -10,22 +17,24 @@ Description ----------- This script is run from scheduler periodically, sending notification on -health related events: +health related events. Monitoring CPU and RAM utilization (available +processing and memory resources) works on all devices: * high CPU utilization -* low available free RAM +* 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 * power supply failed or recovered * temperature is above or below threshold -Note that bad initial state will not trigger an event. - -Monitoring CPU utilization and available free RAM works on all devices. -Other than that only sensors available in hardware can be checked. See what -your hardware supports: - - /system/health/print; +> ⚠️ **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. ### Sample notifications @@ -34,10 +43,10 @@ your hardware supports:   -#### Available free RAM +#### RAM utilization (low available RAM) - - + + #### Voltage @@ -50,8 +59,8 @@ your hardware supports: #### PSU state - - + + Requirements and installation ----------------------------- @@ -65,6 +74,30 @@ Just install the script and create a scheduler: > 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; + Configuration ------------- @@ -80,7 +113,9 @@ The configuration goes to `global-config-overlay`, these are the parameters: Also notification settings are required for [e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md) and/or +[gotify](mod/notification-gotify.md), +[matrix](mod/notification-matrix.md), +[ntfy](mod/notification-ntfy.md) and/or [telegram](mod/notification-telegram.md). --- diff --git a/doc/check-lte-firmware-upgrade.md b/doc/check-lte-firmware-upgrade.md index a81ca86..a0c441e 100644 --- a/doc/check-lte-firmware-upgrade.md +++ b/doc/check-lte-firmware-upgrade.md @@ -1,6 +1,13 @@ Notify on LTE firmware upgrade ============================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -37,7 +44,9 @@ Configuration Also notification settings are required for [e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md) and/or +[gotify](mod/notification-gotify.md), +[matrix](mod/notification-matrix.md), +[ntfy](mod/notification-ntfy.md) and/or [telegram](mod/notification-telegram.md). See also diff --git a/doc/check-perpetual-license.d/notification.avif b/doc/check-perpetual-license.d/notification.avif Binary files differnew file mode 100644 index 0000000..70ca603 --- /dev/null +++ b/doc/check-perpetual-license.d/notification.avif diff --git a/doc/check-perpetual-license.md b/doc/check-perpetual-license.md new file mode 100644 index 0000000..0335fb5 --- /dev/null +++ b/doc/check-perpetual-license.md @@ -0,0 +1,71 @@ +Check perpetual license on CHR +============================== + +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 +----------- + +On *Cloud Hosted Router* (*CHR*) the licensing is perpetual: Buy once, use +forever - but it needs regular renewal. This script checks licensing state +and sends a notification to warn before expiration. + +### Sample notification + + + +Requirements and installation +----------------------------- + +Just install the script: + + $ScriptInstallUpdate check-perpetual-license; + +And add a scheduler for automatic update notification: + + /system/scheduler/add interval=1d name=check-perpetual-license on-event="/system/script/run check-perpetual-license;" start-time=startup; + +Configuration +------------- + +No extra configuration is required for this script, but notification +settings are required for +[e-mail](mod/notification-email.md), +[gotify](mod/notification-gotify.md), +[matrix](mod/notification-matrix.md), +[ntfy](mod/notification-ntfy.md) and/or +[telegram](mod/notification-telegram.md). + +Usage and invocation +-------------------- + +Be notified when run from scheduler or run it manually: + + /system/script/run check-perpetual-license; + +Tips & Tricks +------------- + +The script checks for full connectivity before acting, so scheduling at +startup is perfectly valid: + + /system/scheduler/add name=check-perpetual-license@startup on-event="/system/script/run check-perpetual-license;" start-time=startup; + +See also +-------- + +* [Notify on RouterOS update](check-routeros-update.md) + +--- +[⬅️ Go back to main README](../README.md) +[⬆️ Go back to top](#top) diff --git a/doc/check-routeros-update.md b/doc/check-routeros-update.md index 3b5c2bd..a45e075 100644 --- a/doc/check-routeros-update.md +++ b/doc/check-routeros-update.md @@ -1,6 +1,13 @@ Notify on RouterOS update ========================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -23,8 +30,8 @@ automatically is supported. > ⚠️ **Warning**: Installing updates is important from a security point > of view. At the same time it can be source of serve breakage. So test > versions in lab and read -> [changelog](https://mikrotik.com/download/changelogs/) and -> [forum](https://forum.mikrotik.com/viewforum.php?f=21) before deploying +> [changelog ↗️](https://mikrotik.com/download/changelogs/) and +> [forum ↗️](https://forum.mikrotik.com/viewforum.php?f=21) before deploying > to your production environment! Automatic updates should be handled > with care! @@ -66,7 +73,9 @@ The configuration goes to `global-config-overlay`, these are the parameters: Also notification settings are required for [e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md) and/or +[gotify](mod/notification-gotify.md), +[matrix](mod/notification-matrix.md), +[ntfy](mod/notification-ntfy.md) and/or [telegram](mod/notification-telegram.md). Usage and invocation @@ -91,6 +100,7 @@ startup is perfectly valid: See also -------- +* [Check perpetual license on CHR](check-perpetual-license.md) * [Automatically upgrade firmware and reboot](firmware-upgrade-reboot.md) * [Manage system update](packages-update.md) diff --git a/doc/collect-wireless-mac.md b/doc/collect-wireless-mac.md index 9ba1e3a..2378fed 100644 --- a/doc/collect-wireless-mac.md +++ b/doc/collect-wireless-mac.md @@ -1,6 +1,13 @@ Collect MAC addresses in wireless access list ============================================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -22,13 +29,13 @@ and modify it to your needs. Requirements and installation ----------------------------- -Depending on whether you use `wifiwave2` package (`/interface/wifiwave2`) -or legacy wifi with CAPsMAN (`/caps-man`) or local wireless interface +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. -For `wifiwave2`: +For `wifi`: - $ScriptInstallUpdate collect-wireless-mac.capsman.wifiwave2; + $ScriptInstallUpdate collect-wireless-mac.wifi; For legacy CAPsMAN: @@ -47,7 +54,9 @@ entries are to be added. Also notification settings are required for [e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md) and/or +[gotify](mod/notification-gotify.md), +[matrix](mod/notification-matrix.md), +[ntfy](mod/notification-ntfy.md) and/or [telegram](mod/notification-telegram.md). Usage and invocation diff --git a/doc/daily-psk.md b/doc/daily-psk.md index 60a645f..118d768 100644 --- a/doc/daily-psk.md +++ b/doc/daily-psk.md @@ -1,6 +1,13 @@ Use wireless network with daily psk =================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -21,16 +28,16 @@ Requirements and installation Just install this script. -Depending on whether you use `wifiwave2` package (`/interface/wifiwave2`) -or legacy wifi with CAPsMAN (`/caps-man`) or local wireless interface +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: -For `wifiwave2`: +For `wifi`: - $ScriptInstallUpdate daily-psk.wifiwave2; - /system/scheduler/add interval=1d name=daily-psk on-event="/system/script/run daily-psk.wifiwave2;" start-time=03:00:00; - /system/scheduler/add name=daily-psk@startup on-event="/system/script/run daily-psk.wifiwave2;" start-time=startup; + $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: @@ -58,9 +65,9 @@ The configuration goes to `global-config-overlay`, these are the parameters: > [`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. For `wifiwave2`: +Then add an access list entry. For `wifi`: - /interface/wifiwave2/access-list/add comment="Daily PSK" ssid-regexp="-guest\$" passphrase="ToBeChangedDaily"; + /interface/wifi/access-list/add comment="Daily PSK" ssid-regexp="-guest\$" passphrase="ToBeChangedDaily"; For legacy CAPsMAN: @@ -72,7 +79,9 @@ For legacy local interface: Also notification settings are required for [e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md) and/or +[gotify](mod/notification-gotify.md), +[trix](mod/notification-matrix.md), +[ntfy](mod/notification-ntfy.md) and/or [telegram](mod/notification-telegram.md). --- diff --git a/doc/dhcp-lease-comment.md b/doc/dhcp-lease-comment.md index cd29641..b02f199 100644 --- a/doc/dhcp-lease-comment.md +++ b/doc/dhcp-lease-comment.md @@ -1,6 +1,13 @@ Comment DHCP leases with info from access list ============================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -15,13 +22,13 @@ from wireless access list. Requirements and installation ----------------------------- -Depending on whether you use `wifiwave2` package (`/interface/wifiwave2`) -or legacy wifi with CAPsMAN (`/caps-man`) or local wireless interface +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. -For `wifiwave2`: +For `wifi`: - $ScriptInstallUpdate dhcp-lease-comment.wifiwave2; + $ScriptInstallUpdate dhcp-lease-comment.wifi; For legacy CAPsMAN: diff --git a/doc/dhcp-to-dns.md b/doc/dhcp-to-dns.md index 772704e..4211d85 100644 --- a/doc/dhcp-to-dns.md +++ b/doc/dhcp-to-dns.md @@ -1,6 +1,13 @@ Create DNS records for DHCP leases ================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/firmware-upgrade-reboot.md b/doc/firmware-upgrade-reboot.md index 8bf4d19..54f1da0 100644 --- a/doc/firmware-upgrade-reboot.md +++ b/doc/firmware-upgrade-reboot.md @@ -1,6 +1,13 @@ Automatically upgrade firmware and reboot ========================================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/fw-addr-lists.md b/doc/fw-addr-lists.md index 5805905..d09383b 100644 --- a/doc/fw-addr-lists.md +++ b/doc/fw-addr-lists.md @@ -1,6 +1,13 @@ Download, import and update firewall address-lists ================================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -11,10 +18,11 @@ 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 -lists from [abuse.ch](https://abuse.ch/) and -[dshield.org](https://dshield.org/), and -lists from [spamhaus.org](https://spamhaus.org/) are prepared. +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. @@ -22,6 +30,10 @@ 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 ----------------------------- @@ -51,13 +63,16 @@ The configuration goes to `global-config-overlay`, these are the parameters: > 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. Some certificates are -available in my repository and downloaded automatically. Import it manually -(menu `/certificate/`) if missing. +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. This rejects the packets from and to ip addresses listed -in address-list `block`. +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; @@ -85,6 +100,38 @@ Alternatively handle the packets in firewall's raw section if you prefer: > ⚠️ **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 39921db..799cae7 100644 --- a/doc/global-wait.md +++ b/doc/global-wait.md @@ -1,6 +1,13 @@ Wait for global functions and modules ===================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/gps-track.md b/doc/gps-track.md index 721b075..5e4878f 100644 --- a/doc/gps-track.md +++ b/doc/gps-track.md @@ -1,6 +1,13 @@ Send GPS position to server =========================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/hotspot-to-wpa.md b/doc/hotspot-to-wpa.md index 4909eef..a2e9748 100644 --- a/doc/hotspot-to-wpa.md +++ b/doc/hotspot-to-wpa.md @@ -1,5 +1,12 @@ -Use WPA2 network with hotspot credentials -========================================= +Use WPA network with hotspot credentials +======================================== + +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) [⬅️ Go back to main README](../README.md) @@ -10,23 +17,24 @@ Description ----------- RouterOS supports an unlimited number of MAC address specific passphrases -for WPA2 encrypted wifi networks via access list. The idea of this script -is to transfer hotspot credentials to MAC address specific WPA2 passphrase. +for WPA encrypted wifi networks via access list. The idea of this script +is to transfer hotspot credentials to MAC address specific WPA passphrase. Requirements and installation ----------------------------- -You need a properly configured hotspot on one (open) SSID and a WP2 enabled +You need a properly configured hotspot on one (open) SSID and a WPA enabled SSID with suffix "`-wpa`". -Then install the script. Depending on whether you use `wifiwave2` package -(`/interface/wifiwave2`) 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. +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. -For `wifiwave2`: +For `wifi`: - $ScriptInstallUpdate hotspot-to-wpa.wifiwave2; - /ip/hotspot/user/profile/set on-login="hotspot-to-wpa.wifiwave2" [ find ]; + $ScriptInstallUpdate hotspot-to-wpa.wifi; + /ip/hotspot/user/profile/set on-login="hotspot-to-wpa.wifi" [ find ]; For legacy CAPsMAN: @@ -39,10 +47,10 @@ 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. -For `wifiwave2`: +For `wifi`: - $ScriptInstallUpdate hotspot-to-wpa-cleanup.wifiwave2,lease-script; - /system/scheduler/add interval=1d name=hotspot-to-wpa-cleanup on-event="/system/script/run hotspot-to-wpa-cleanup.wifiwave2;" start-time=startup; + $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; For legacy CAPsMAN: @@ -78,17 +86,17 @@ Additional information is not available, including the password. Additionally templates can be created to give more options for access list: * `action`: set to `reject` to ignore logins on that hotspot -* `private-passphrase`: do **not** use passphrase from hotspot's user - credentials, but given one - or unset (use default passphrase) with - special word `ignore` +* `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` * `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 -`wifiwave2`: +For a hotspot called `example` the template could look like this. +For `wifi`: - /interface/wifiwave2/access-list/add comment="hotspot-to-wpa template example" disabled=yes private-passphrase="ignore" ssid-regexp="^example\$" vlan-id=10; + /interface/wifi/access-list/add comment="hotspot-to-wpa template example" disabled=yes passphrase="ignore" ssid-regexp="^example\$" vlan-id=10; For legacy CAPsMAN: @@ -103,7 +111,7 @@ 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 WPA2 network, using the +(identified by MAC address) can connect to the WPA network, using the passphrase from hotspot credentials. See also diff --git a/doc/ip-addr-bridge.md b/doc/ip-addr-bridge.md index a759829..f9f98e3 100644 --- a/doc/ip-addr-bridge.md +++ b/doc/ip-addr-bridge.md @@ -1,6 +1,13 @@ Manage IP addresses with bridge status ====================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) + [⬅️ Go back to main README](../README.md) Description diff --git a/doc/ipsec-to-dns.md b/doc/ipsec-to-dns.md index 04500a3..123656c 100644 --- a/doc/ipsec-to-dns.md +++ b/doc/ipsec-to-dns.md @@ -1,6 +1,13 @@ Create DNS records for IPSec peers ================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/ipv6-update.md b/doc/ipv6-update.md index f49cfaa..1f009b1 100644 --- a/doc/ipv6-update.md +++ b/doc/ipv6-update.md @@ -1,6 +1,13 @@ Update configuration on IPv6 prefix change ========================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -38,13 +45,17 @@ Installing [ppp-on-up](ppp-on-up.md) may solve this. Configuration ------------- -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: +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. + +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; -As this entry is mandatory it is created automatically if it does not exist, -with the comment also set for list. +If the dynamic entry exists already you need to remove it before creating +the static one.. 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 diff --git a/doc/lease-script.md b/doc/lease-script.md index 55928fc..f83c383 100644 --- a/doc/lease-script.md +++ b/doc/lease-script.md @@ -1,6 +1,13 @@ Run other scripts on DHCP lease =============================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -40,7 +47,7 @@ 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 WPA2 network with hotspot credentials](hotspot-to-wpa.md) +* [Use WPA network with hotspot credentials](hotspot-to-wpa.md) --- [⬅️ Go back to main README](../README.md) diff --git a/doc/leds-mode.md b/doc/leds-mode.md index 5710d85..a194396 100644 --- a/doc/leds-mode.md +++ b/doc/leds-mode.md @@ -1,6 +1,13 @@ Manage LEDs dark mode ===================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) + [⬅️ Go back to main README](../README.md) Description diff --git a/doc/log-forward.md b/doc/log-forward.md index 43ae897..f6086c8 100644 --- a/doc/log-forward.md +++ b/doc/log-forward.md @@ -1,6 +1,13 @@ Forward log messages via notification ===================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -15,15 +22,15 @@ server (see `/system/logging`). This has some limitation, however: * does not work early after boot if network connectivity is not yet established, or breaks intermittently * lots of messages generate a flood of mails -* Matrix and Telegram are not supported +* Gotify, Matrix, Ntfy 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* and *Telegram* - when installed and configured, see below) +* forward via notification (which includes *e-mail*, *Gotify*, *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. @@ -46,6 +53,12 @@ Just install the script: 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 @@ -59,7 +72,7 @@ The configuration goes to `global-config-overlay`, these are the parameters: > 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). +[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: @@ -67,7 +80,9 @@ To forward **all** (ignoring severity) log messages with topics `account` Also notification settings are required for [e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md) and/or +[gotify](mod/notification-gotify.md), +[matrix](mod/notification-matrix.md), +[ntfy](mod/notification-ntfy.md) and/or [telegram](mod/notification-telegram.md). Tips & Tricks diff --git a/doc/mod/bridge-port-to.md b/doc/mod/bridge-port-to.md index a956de4..629c526 100644 --- a/doc/mod/bridge-port-to.md +++ b/doc/mod/bridge-port-to.md @@ -1,6 +1,13 @@ Manage ports in bridge ====================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/mod/bridge-port-vlan.md b/doc/mod/bridge-port-vlan.md index 068f050..cf29199 100644 --- a/doc/mod/bridge-port-vlan.md +++ b/doc/mod/bridge-port-vlan.md @@ -1,6 +1,13 @@ Manage VLANs on bridge ports ============================ +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/mod/inspectvar.md b/doc/mod/inspectvar.md index 1cc49a2..7daba15 100644 --- a/doc/mod/inspectvar.md +++ b/doc/mod/inspectvar.md @@ -1,6 +1,13 @@ Inspect variables ================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/mod/ipcalc.md b/doc/mod/ipcalc.md index f2f1140..c07853e 100644 --- a/doc/mod/ipcalc.md +++ b/doc/mod/ipcalc.md @@ -1,6 +1,13 @@ IP address calculation ====================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/mod/notification-email.md b/doc/mod/notification-email.md index 8dc2ecf..127bf96 100644 --- a/doc/mod/notification-email.md +++ b/doc/mod/notification-email.md @@ -1,6 +1,13 @@ Send notifications via e-mail ============================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -25,7 +32,7 @@ Configuration ------------- Set up your device's -[e-mail settings](https://wiki.mikrotik.com/wiki/Manual:Tools/email). +[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. @@ -51,12 +58,12 @@ 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..." + $SendEMail "Subject..." "Body..."; Alternatively this sends a notification with all available and configured methods: - $SendNotification "Subject..." "Body..." + $SendNotification "Subject..." "Body..."; To use the functions in your own scripts you have to declare them first. Place this before you call them: @@ -72,7 +79,9 @@ function available: See also -------- +* [Send notifications via Gotify](notification-gotify.md) * [Send notifications via Matrix](notification-matrix.md) +* [Send notifications via Ntfy](notification-ntfy.md) * [Send notifications via Telegram](notification-telegram.md) --- diff --git a/doc/mod/notification-gotify.d/appsetup.avif b/doc/mod/notification-gotify.d/appsetup.avif Binary files differnew file mode 100644 index 0000000..58f57a8 --- /dev/null +++ b/doc/mod/notification-gotify.d/appsetup.avif diff --git a/doc/mod/notification-gotify.md b/doc/mod/notification-gotify.md new file mode 100644 index 0000000..6fce629 --- /dev/null +++ b/doc/mod/notification-gotify.md @@ -0,0 +1,97 @@ +Send notifications via Gotify +=========================== + +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 +[Gotify ↗️](https://gotify.net/). 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-gotify; + +Also deploy the [Gotify server ↗️](https://github.com/gotify/server) and +optionally install a Gotify client on your mobile device. + +Configuration +------------- + +Follow the [Installation ↗️](https://gotify.net/docs/install) instructions +and the [First Login ↗️](https://gotify.net/docs/first-login) setup. Once +you have a user and account you can start creating apps. Each app is an +independent notification feed for a device or application. + + + +On creation apps are assigned a *Token* for authentification, you will need +that in configuration. + +Edit `global-config-overlay`, add `GotifyServer` with your server address +(just the address, no protocol - `https://` is assumed) and `GotifyToken` +with the *Token* from your configured app on the Gotify server. 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. + +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 Gotify application feed. + +But of course you can use the function to send notifications directly. Give +it a try: + + $SendGotify "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 SendGotify; + :global SendNotification; + +In case there is a situation when the queue needs to be purged there is a +function available: + + $PurgeGotifyQueue; + +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 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 Binary files differnew file mode 100644 index 0000000..b897943 --- /dev/null +++ b/doc/mod/notification-matrix.d/01-authenticate.avif diff --git a/doc/mod/notification-matrix.d/01-home-server.avif b/doc/mod/notification-matrix.d/01-home-server.avif Binary files differdeleted file mode 100644 index 683c7b5..0000000 --- a/doc/mod/notification-matrix.d/01-home-server.avif +++ /dev/null diff --git a/doc/mod/notification-matrix.d/02-access-token.avif b/doc/mod/notification-matrix.d/02-access-token.avif Binary files differdeleted file mode 100644 index 54109a6..0000000 --- a/doc/mod/notification-matrix.d/02-access-token.avif +++ /dev/null diff --git a/doc/mod/notification-matrix.d/02-join-room.avif b/doc/mod/notification-matrix.d/02-join-room.avif Binary files differnew file mode 100644 index 0000000..ad99ffd --- /dev/null +++ b/doc/mod/notification-matrix.d/02-join-room.avif diff --git a/doc/mod/notification-matrix.d/03-join-room.avif b/doc/mod/notification-matrix.d/03-join-room.avif Binary files differdeleted file mode 100644 index 45974b8..0000000 --- a/doc/mod/notification-matrix.d/03-join-room.avif +++ /dev/null diff --git a/doc/mod/notification-matrix.md b/doc/mod/notification-matrix.md index ab8efc9..da6d6de 100644 --- a/doc/mod/notification-matrix.md +++ b/doc/mod/notification-matrix.md @@ -1,6 +1,13 @@ Send notifications via Matrix ============================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -10,7 +17,7 @@ Description ----------- This module adds support for sending notifications via -[Matrix](https://matrix.org/) via client server api. A queue is used to +[Matrix ↗️](https://matrix.org/) via client server api. A queue is used to make sure notifications are not lost on failure but sent later. Requirements and installation @@ -21,8 +28,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. As there is no privilege separation you should create a dedicated -notification account, in addition to your general user account. +devices. Create and setup an account there, we will reference that as +"*general account*" later. Configuration ------------- @@ -35,58 +42,62 @@ reload the configuration. > [`global-config`](../../global-config.rsc) (the one without `-overlay`) to > your local `global-config-overlay` and modify it to your specific needs. -### Home server +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. -Matrix user accounts are identified by a unique user id in the form of -`@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). +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"; -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: +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). - /tool/fetch "https://matrix.org/.well-known/matrix/client" output=user; +### 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. -So the home server for "*matrix.org*" is "*matrix-client.matrix.org*". -Please strip the protocol ("*https://*") for `MatrixHomeServer` if given. +### Setup new account -### Access token +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. -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). +#### Authenticate + +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: -We use the home server discovered above, "*matrix-client.matrix.org*". -The user is "*example*" and password is "*v3ry-s3cr3t*". + $SetupMatrixAuthenticate "@example:matrix.org" "v3ry-s3cr3t"; - /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 written to a new configuration snippet +`global-config-overlay.d/mod/notification-matrix`. -The server replied with a JSON object containing the `access_token`, use that -for `MatrixAccessToken`. +#### Join Room -### Room +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). -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 make the *notification account* join into the room by accepting +the invite. -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. + $SetupMatrixJoinRoom "!WUcxpSjKyxSGelouhA:matrix.org"; - /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; + - +The configuration is appended to the configuration snippet +`global-config-overlay.d/mod/notification-matrix`. Usage and invocation -------------------- @@ -97,12 +108,12 @@ 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..." + $SendMatrix "Subject..." "Body..."; Alternatively this sends a notification with all available and configured methods: - $SendNotification "Subject..." "Body..." + $SendNotification "Subject..." "Body..."; To use the functions in your own scripts you have to declare them first. Place this before you call them: @@ -118,7 +129,10 @@ function available: See also -------- +* [Certificate name from browser](../../CERTIFICATES.md) * [Send notifications via e-mail](notification-email.md) +* [Send notifications via Gotify](notification-gotify.md) +* [Send notifications via Ntfy](notification-ntfy.md) * [Send notifications via Telegram](notification-telegram.md) --- diff --git a/doc/mod/notification-ntfy.md b/doc/mod/notification-ntfy.md new file mode 100644 index 0000000..993501d --- /dev/null +++ b/doc/mod/notification-ntfy.md @@ -0,0 +1,99 @@ +Send notifications via Ntfy +=========================== + +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 Gotify](notification-gotify.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 Binary files differnew file mode 100644 index 0000000..7792969 --- /dev/null +++ b/doc/mod/notification-telegram.d/getchatid.avif diff --git a/doc/mod/notification-telegram.md b/doc/mod/notification-telegram.md index 89659c8..804104f 100644 --- a/doc/mod/notification-telegram.md +++ b/doc/mod/notification-telegram.md @@ -1,6 +1,13 @@ Send notifications via Telegram =============================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -10,7 +17,7 @@ Description ----------- This module adds support for sending notifications via -[Telegram](https://telegram.org/) via bot api. A queue is used to make sure +[Telegram ↗️](https://telegram.org/) via bot api. A queue is used to make sure notifications are not lost on failure but sent later. Requirements and installation @@ -26,19 +33,26 @@ and create an account. Configuration ------------- -Open Telegram, then start a chat with [BotFather](https://t.me/BotFather) and +Open Telegram, then start a chat with [BotFather ↗️](https://t.me/BotFather) and create your own bot:  -Now open a chat with your bot and start it by clicking the `START` button. +Set that token from *BotFather* (use your own!) to `TelegramTokenId`, for +now just temporarily: + + :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: -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`. + $GetTelegramChatId; + + Finally edit `global-config-overlay`, add `TelegramTokenId` with the token -from *BotFather* and `TelegramChatId` with your id from *GetIDs Bot*. Then +from *BotFather* and `TelegramChatId` with your retrieved chat id. Then reload the configuration. > ℹ️ **Info**: Copy relevant configuration from @@ -47,9 +61,13 @@ reload the configuration. ### Notifications to 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. +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. Usage and invocation -------------------- @@ -60,12 +78,12 @@ 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..." + $SendTelegram "Subject..." "Body..."; Alternatively this sends a notification with all available and configured methods: - $SendNotification "Subject..." "Body..." + $SendNotification "Subject..." "Body..."; To use the functions in your own scripts you have to declare them first. Place this before you call them: @@ -84,7 +102,7 @@ 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. +chat with [BotFather ↗️](https://t.me/BotFather) and set it there.  @@ -97,7 +115,9 @@ 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 Gotify](notification-gotify.md) * [Send notifications via Matrix](notification-matrix.md) +* [Send notifications via Ntfy](notification-ntfy.md) --- [⬅️ Go back to main README](../../README.md) diff --git a/doc/mod/scriptrunonce.md b/doc/mod/scriptrunonce.md index 895c20c..955d12e 100644 --- a/doc/mod/scriptrunonce.md +++ b/doc/mod/scriptrunonce.md @@ -1,6 +1,13 @@ Download script and run it once =============================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/mod/ssh-keys-import.md b/doc/mod/ssh-keys-import.md index cf28ee2..344f4bc 100644 --- a/doc/mod/ssh-keys-import.md +++ b/doc/mod/ssh-keys-import.md @@ -1,6 +1,13 @@ Import ssh keys for public key authentication ============================================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -27,15 +34,15 @@ Usage and invocation Call the function `$SSHKeysImport` with key and user as parameter to import that key: - $SSHKeysImport "ssh-rsa AAAAB3Nza...QYZk8= user" admin; - -Starting with RouterOS *7.12beta1* support for keys of type `ed25519` has -been added: - $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. +`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 diff --git a/doc/mode-button.md b/doc/mode-button.md index 6374b83..be15bc9 100644 --- a/doc/mode-button.md +++ b/doc/mode-button.md @@ -1,6 +1,13 @@ Mode button with multiple presses ================================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -52,7 +59,7 @@ Configuration The configuration goes to `global-config-overlay`, these are the parameters: * `ModeButton`: an array with defined actions -* `ModeButtonLED`: led to give visual feedback +* `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 diff --git a/doc/netwatch-dns.md b/doc/netwatch-dns.md index a208d00..0d94918 100644 --- a/doc/netwatch-dns.md +++ b/doc/netwatch-dns.md @@ -1,6 +1,13 @@ Manage DNS and DoH servers from netwatch ======================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -43,12 +50,25 @@ Giving a specific query url for DoH is possible: Note that using a name in DoH url may introduce a chicken-and-egg issue! -Importing a certificate automatically is possible, at least if available in -the repository (see `certs` sub directory). +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; - /tool/netwatch/add comment="doh, doh-cert=DigiCert TLS Hybrid ECC SHA384 2020 CA1" host=1.1.1.1; - /tool/netwatch/add comment="doh, doh-cert=DigiCert TLS Hybrid ECC SHA384 2020 CA1" host=9.9.9.9; - /tool/netwatch/add comment="doh, doh-cert=GTS CA 1C3" 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: @@ -71,6 +91,7 @@ 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) --- diff --git a/doc/netwatch-notify.md b/doc/netwatch-notify.md index 6df233b..91c568f 100644 --- a/doc/netwatch-notify.md +++ b/doc/netwatch-notify.md @@ -1,6 +1,13 @@ Notify on host up and down ========================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -40,7 +47,9 @@ The hosts to be checked have to be added to netwatch with specific comment: Also notification settings are required for [e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md) and/or +[gotify](mod/notification-gotify.md), +[matrix](mod/notification-matrix.md), +[ntfy](mod/notification-ntfy.md) and/or [telegram](mod/notification-telegram.md). ### Hooks @@ -58,9 +67,9 @@ 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; @@ -73,18 +82,23 @@ suppress notification if the parent host is down: /tool/netwatch/add comment="notify, name=example.com, parent=gateway" host=93.184.216.34; Note that every configured parent in a chain increases the check count -threshould by one. +threshold 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"; + /tool/netwatch/add comment="notify, name=example.com, resolve=example.com" host=0.0; + +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: -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. + /tool/netwatch/add comment="notify, name=example.com, resolve=example.com" host=::; ### No notification on host down @@ -96,6 +110,15 @@ powered off, but accessibility is of interest. 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 @@ -103,6 +126,13 @@ 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; + Tips & Tricks ------------- diff --git a/doc/ospf-to-leds.md b/doc/ospf-to-leds.md index ce7f9ff..3694d35 100644 --- a/doc/ospf-to-leds.md +++ b/doc/ospf-to-leds.md @@ -1,6 +1,13 @@ Visualize OSPF state via LEDs ============================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/packages-update.md b/doc/packages-update.md index 653d233..75225fe 100644 --- a/doc/packages-update.md +++ b/doc/packages-update.md @@ -1,6 +1,13 @@ Manage system update ==================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -34,6 +41,21 @@ 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 -------------------- diff --git a/doc/ppp-on-up.md b/doc/ppp-on-up.md index d5d0ae5..305afc1 100644 --- a/doc/ppp-on-up.md +++ b/doc/ppp-on-up.md @@ -1,6 +1,13 @@ Run scripts on ppp connection ============================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/sms-action.md b/doc/sms-action.md index 3391902..b696c85 100644 --- a/doc/sms-action.md +++ b/doc/sms-action.md @@ -1,6 +1,13 @@ Act on received SMS =================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/sms-forward.md b/doc/sms-forward.md index a323157..0c1317d 100644 --- a/doc/sms-forward.md +++ b/doc/sms-forward.md @@ -1,6 +1,13 @@ Forward received SMS ==================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -40,7 +47,8 @@ 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. + 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 @@ -48,7 +56,9 @@ The configuration goes to `global-config-overlay`, this is the only parameter: Notification settings are required for [e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md) and/or +[gotify](mod/notification-gotify.md), +[matrix](mod/notification-matrix.md), +[ntfy](mod/notification-ntfy.md) and/or [telegram](mod/notification-telegram.md). Tips & Tricks diff --git a/doc/super-mario-theme.md b/doc/super-mario-theme.md index b3100e8..c72f220 100644 --- a/doc/super-mario-theme.md +++ b/doc/super-mario-theme.md @@ -1,6 +1,13 @@ Play Super Mario theme ====================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) + [⬅️ Go back to main README](../README.md) Description diff --git a/doc/telegram-chat.d/01-chat-specific.avif b/doc/telegram-chat.d/01-chat-specific.avif Binary files differindex 387dc3a..ab75f78 100644 --- a/doc/telegram-chat.d/01-chat-specific.avif +++ b/doc/telegram-chat.d/01-chat-specific.avif diff --git a/doc/telegram-chat.d/02-chat-all.avif b/doc/telegram-chat.d/02-chat-all.avif Binary files differindex 32fc181..ed1a389 100644 --- a/doc/telegram-chat.d/02-chat-all.avif +++ b/doc/telegram-chat.d/02-chat-all.avif diff --git a/doc/telegram-chat.d/03-reply.avif b/doc/telegram-chat.d/03-reply.avif Binary files differnew file mode 100644 index 0000000..515853e --- /dev/null +++ b/doc/telegram-chat.d/03-reply.avif diff --git a/doc/telegram-chat.md b/doc/telegram-chat.md index 391042d..1e6f70f 100644 --- a/doc/telegram-chat.md +++ b/doc/telegram-chat.md @@ -1,6 +1,13 @@ Chat with your router and send commands via Telegram bot ======================================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 @@ -46,6 +53,8 @@ parameters: 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 @@ -63,6 +72,21 @@ act on your commands. 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. + + + +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 ----------------- @@ -83,6 +107,14 @@ scripting capabilities. Try to print what you want to act on... /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 @@ -98,9 +130,8 @@ send information on its own. Something like this should do the job: ### Output size -RouterOS is limited in reading file content to a size of about four -kilobytes. Reading larger files does just fail, and that is also the limit -for command output. +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 diff --git a/doc/unattended-lte-firmware-upgrade.md b/doc/unattended-lte-firmware-upgrade.md index 8bc9e71..cb96aa1 100644 --- a/doc/unattended-lte-firmware-upgrade.md +++ b/doc/unattended-lte-firmware-upgrade.md @@ -1,6 +1,13 @@ Install LTE firmware upgrade ============================ +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) + [⬅️ Go back to main README](../README.md) Description @@ -12,6 +19,7 @@ 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. diff --git a/doc/update-gre-address.md b/doc/update-gre-address.md index 5bf95bd..de9f622 100644 --- a/doc/update-gre-address.md +++ b/doc/update-gre-address.md @@ -1,6 +1,13 @@ Update GRE configuration with dynamic addresses =============================================== +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/doc/update-tunnelbroker.md b/doc/update-tunnelbroker.md index 4d6f1ba..ee0fe98 100644 --- a/doc/update-tunnelbroker.md +++ b/doc/update-tunnelbroker.md @@ -1,6 +1,13 @@ Update tunnelbroker configuration ================================= +[](https://github.com/eworm-de/routeros-scripts/stargazers) +[](https://github.com/eworm-de/routeros-scripts/network) +[](https://github.com/eworm-de/routeros-scripts/watchers) +[](https://mikrotik.com/download/changelogs/) +[](https://t.me/routeros_scripts) +[](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 diff --git a/firmware-upgrade-reboot.rsc b/firmware-upgrade-reboot.rsc index ec2babc..86a9a8c 100644 --- a/firmware-upgrade-reboot.rsc +++ b/firmware-upgrade-reboot.rsc @@ -1,45 +1,60 @@ #!rsc by RouterOS # RouterOS script: firmware-upgrade-reboot -# Copyright (c) 2022-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2022-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # install firmware upgrade, and reboot -# https://git.eworm.de/cgit/routeros-scripts/about/doc/firmware-upgrade-reboot.md +# https://rsc.eworm.de/doc/firmware-upgrade-reboot.md -:local 0 "firmware-upgrade-reboot"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global LogPrintExit2; -:global ScriptLock; -:global VersionToNum; - -$ScriptLock $0; - -:local RouterBoard [ /system/routerboard/get ]; -:if ($RouterBoard->"current-firmware" = $RouterBoard->"upgrade-firmware") do={ - $LogPrintExit2 info $0 ("Current and upgrade firmware match with version " . \ - $RouterBoard->"current-firmware" . ".") true; -} -:if ([ $VersionToNum ($RouterBoard->"current-firmware") ] > [ $VersionToNum ($RouterBoard->"upgrade-firmware") ]) do={ - $LogPrintExit2 info $0 ("Different firmware version is available, but it is a downgrade. Ignoring.") true; -} - -:if ([ /system/routerboard/settings/get auto-upgrade ] = false) do={ - $LogPrintExit2 info $0 ("Firmware version " . $RouterBoard->"upgrade-firmware" . \ - " is available, upgrading.") false; - /system/routerboard/upgrade; +: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 ]; } - -: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/fw-addr-lists.d/allow b/fw-addr-lists.d/allow new file mode 100644 index 0000000..8b59ed7 --- /dev/null +++ b/fw-addr-lists.d/allow @@ -0,0 +1,3 @@ +# 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 new file mode 100644 index 0000000..5e9fef2 --- /dev/null +++ b/fw-addr-lists.d/block @@ -0,0 +1,5 @@ +# 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 new file mode 100644 index 0000000..3b31a94 --- /dev/null +++ b/fw-addr-lists.d/mikrotik @@ -0,0 +1,5 @@ +# 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 index 38fdd96..f0940fe 100644 --- a/fw-addr-lists.rsc +++ b/fw-addr-lists.rsc @@ -1,109 +1,214 @@ #!rsc by RouterOS # RouterOS script: fw-addr-lists -# Copyright (c) 2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2023-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.16 # # download, import and update firewall address-lists -# https://git.eworm.de/cgit/routeros-scripts/about/doc/fw-addr-lists.md +# https://rsc.eworm.de/doc/fw-addr-lists.md -:local 0 "fw-addr-lists"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global FwAddrLists; -:global FwAddrListTimeOut; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; + + :global FwAddrLists; + :global FwAddrListTimeOut; -:global CertificateAvailable; -:global LogPrintExit2; -:global ScriptLock; -:global WaitFullyConnected; + :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 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; + } } } -} -$ScriptLock $0; -$WaitFullyConnected; + :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); -:local ListComment ("managed by " . $0); + :foreach FwListName,FwList in=$FwAddrLists do={ + :local CntAdd 0; + :local CntRenew 0; + :local CntRemove 0; + :local IPv4Addresses ({}); + :local IPv6Addresses ({}); + :local Failure false; -:foreach FwListName,FwList in=$FwAddrLists do={ - :local Addresses ({}); - :local CntAdd 0; - :local CntRenew 0; - :local CntRemove 0; - :local Failure false; + :foreach List in=$FwList do={ + :local CheckCertificate false; + :local Data false; + :local TimeOut [ $EitherOr [ :totime ($List->"timeout") ] $FwAddrListTimeOut ]; - :foreach List in=$FwList do={ - :local CheckCertificate "no"; - :local Data false; + :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."); + } + } - :if ([ :len ($List->"cert") ] > 0) do={ - :set CheckCertificate "yes-without-crl"; - :if ([ $CertificateAvailable ($List->"cert") ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading required certificate failed, trying anyway.") false; + :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"); + } + } + } } - } - :for I from=2 to=0 do={ :if ($Data = false) do={ - :do { - :set Data ([ /tool/fetch ($List->"url") check-certificate=$CheckCertificate output=user as-value ]->"data"); - } on-error={ - $LogPrintExit2 debug $0 ("Failed downloading, " . $I . " retries pending: " . $List->"url") false; - :delay 2s; + :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={ } } } - :if ($Data = false) do={ - :set Data ""; - :set Failure true; - $LogPrintExit2 warning $0 ("Failed downloading list from: " . $List->"url") false; + :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); + } + } } - :while ([ :len $Data ] != 0) do={ - :local Line [ :pick $Data 0 [ :find $Data "\n" ] ]; - :local Address ([ :pick $Line 0 [ $FindDelim $Line ] ] . ($List->"cidr")); - :if ($Address ~ "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}(/[0-9]{1,2})?\$" || \ - $Address ~ "^[\\.a-zA-Z0-9-]+\\.[a-zA-Z]{2,}\$") do={ - :set ($Addresses->$Address) 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); + } } - :set Data [ :pick $Data ([ :len $Line ] + 1) [ :len $Data ] ]; } - } - :foreach Entry in=[ /ip/firewall/address-list/find where list=$FwListName comment=$ListComment ] do={ - :local Address [ /ip/firewall/address-list/get $Entry address ]; - :if (($Addresses->$Address) = 1) do={ - $LogPrintExit2 debug $0 ("Renewing: " . $Address) false; - /ip/firewall/address-list/set $Entry timeout=$FwAddrListTimeOut; - :set ($Addresses->$Address); - :set CntRenew ($CntRenew + 1); - } else={ - :if ($Failure = false) do={ - $LogPrintExit2 debug $0 ("Removing: " . $Address) false; - /ip/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 Address,Ignore in=$Addresses do={ - $LogPrintExit2 debug $0 ("Adding: " . $Address) false; - :do { - /ip/firewall/address-list/add list=$FwListName comment=$ListComment address=$Address timeout=$FwAddrListTimeOut; - :set ($Addresses->$Address); - :set CntAdd ($CntAdd + 1); - } on-error={ - $LogPrintExit2 warning $0 ("Failed to add address " . $Address . " to list '" . $FwListName . "'.") false; + :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); + } + } } - } - $LogPrintExit2 info $0 ("list: " . $FwListName . " -- added: " . $CntAdd . " - renewed: " . $CntRenew . " - removed: " . $CntRemove) false; + $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-overlay.rsc b/global-config-overlay.rsc index af4b52c..9afaceb 100644 --- a/global-config-overlay.rsc +++ b/global-config-overlay.rsc @@ -1,12 +1,12 @@ # Overlay for global configuration by RouterOS Scripts -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md # # global configuration, custom overlay -# https://git.eworm.de/cgit/routeros-scripts/about/#editing-configuration +# https://rsc.eworm.de/#editing-configuration # Copy relevant configuration from global-config, paste and modify it here. -# https://git.eworm.de/cgit/routeros-scripts/about/global-config.rsc +# https://rsc.eworm.de/global-config.rsc # End of global-config-overlay diff --git a/global-config.changes b/global-config.changes deleted file mode 100644 index 47cfbc6..0000000 --- a/global-config.changes +++ /dev/null @@ -1,123 +0,0 @@ -# News, changes and migration by RouterOS Scripts -# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md - -:global IfThenElse; -:global RequiredRouterOS; - -# 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 no longer required."; - 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=("Finally merged the RouterOS v7 code into the main branch. " . [ $IfThenElse ([ $RequiredRouterOS "global-config.changes" "7.0" false ] = true) \ - ("You may now drop '\$ScriptUpdatesUrlSuffix' from 'global-config-overlay'.") \ - ("Still running RouterOS v6, so last reminder to see https://git.eworm.de/cgit/routeros-scripts/about/#requirements") ]); - 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-v7' branch will now freeze, and vanish any time in future. You already switched to 'main' branch, well done!"; - 81="Dropped script 'rotate-ntp', as the limitation does no longer exist."; - 82="Renamed the comment parameter 'hostname' to just 'name' for 'netwatch-notify'."; - 83="Introduced new setting to disable news and change notifications, dropped version from configuration."; - 84="Support for e-mail notifications moved to a module. It is installed automatically if required."; - 85="Dropped 'netwatch-syslog', filtering in firewall is advised."; - 86="Added support for hooks in 'sms-forward'. This now provides similar functionality to 'sms-action', but is more flexible."; - 87="Added support for extra text (or emojis \F0\9F\9A\80) in notification tags."; - 88="Added support for monitoring CPU utilization and available free RAM in 'check-health'."; - 89="Made the warning time for 'check-certificates' configurable."; - 90="Chat with your router! Introduced 'telegram-chat' to chat via Telegram bot and send commands to your router."; - 91="Dropped check for CAP in 'check-routeros-update' to solve issues with wifiwave2 package."; - 92="Made qr-code url configurable for 'daily-psk'."; - 93="Added support to backup global-config-overlay in 'backup-email' and 'backup-upload'."; - 94="Added support for host addresses in address-list for 'ipv6-update'."; - 95="Renamed script files in repository, running migration. Warnings (one per script) are expected, no user interaction is required."; -}; - -# 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;"; - 81=":global NtpPool; :if ([ :len [ /system/script/find where name=\"rotate-ntp\" ] ] > 0) do={ /system/script/remove [ find where name=\"rotate-ntp\" ]; /system/scheduler/remove [ find where name=\"rotate-ntp\" ]; /system/ntp/client/set servers=\$NtpPool; };"; - 82=":global CharacterReplace; :foreach Netwatch in=[ /tool/netwatch/find where comment~\"notify\" !disabled ] do={ /tool/netwatch/set \$Netwatch comment=[ \$CharacterReplace [ get \$Netwatch comment ] \"hostname=\" \"name=\" ]; };"; - 84=":global ScriptInstallUpdate; :global EmailGeneralTo; :if ([ /tool/e-mail/get address ] != \"0.0.0.0\" && [ :len \$EmailGeneralTo ] > 0) do={ \$ScriptInstallUpdate mod/notification-email; }"; - 95=":global ScriptInstallUpdate; :global CharacterReplace; :foreach Script in=[ /system/script/find where name~\"\\\\.rsc\\\$\" source~\"^#!rsc by RouterOS\\n\" ] do={ /system/script/set \$Script name=[ \$CharacterReplace [ get \$Script name ] \".rsc\" \"\" ]; }; \$ScriptInstallUpdate;"; -}; diff --git a/global-config.rsc b/global-config.rsc index 7d3ef7c..42a457a 100644 --- a/global-config.rsc +++ b/global-config.rsc @@ -1,10 +1,10 @@ #!rsc by RouterOS # RouterOS script: global-config -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md # # global configuration -# https://git.eworm.de/cgit/routeros-scripts/about/ +# https://rsc.eworm.de/ # Set this to 'true' to disable news and change notifications. :global NoNewsAndChangesNotification false; @@ -33,6 +33,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 { @@ -41,8 +43,6 @@ #}; :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,9 +54,24 @@ #:global MatrixAccessToken "123456ABCDEFGHI..."; #:global MatrixRoom "!example:matrix.org"; -# It is possible to override e-mail, Telegram and Matrix setting for every -# script. This is done in arrays, where 'Override' is appended to the -# variable name, like this: +# 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 ""; + +# You can send Gotify notifications. Configure these settings and +# install the module: +# $ScriptInstallUpdate mod/notification-gotify +:global GotifyServer ""; +:global GotifyToken ""; + +# 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: #:global EmailGeneralToOverride { # "check-certificates"="override@example.com"; # "backup-email"="backup@example.com"; @@ -79,42 +94,50 @@ :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://eworm.de/ros/fw-addr-lists/allow"; -# cert="R3" }; +# { url="https://rsc.eworm.de/main/fw-addr-lists.d/allow"; +# cert="ISRG Root X2"; timeout=1w }; # }; "block"={ -# { url="https://eworm.de/ros/fw-addr-lists/block"; -# cert="R3" }; - { url="https://feodotracker.abuse.ch/downloads/ipblocklist_recommended.txt"; - cert="GlobalSign Atlas R3 DV TLS CA 2022 Q3" }; - { url="https://sslbl.abuse.ch/blacklist/sslipblacklist.txt"; - cert="GlobalSign Atlas R3 DV TLS CA 2022 Q3" }; +# { 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="R3" }; -# { url="https://www.spamhaus.org/drop/drop.txt"; -# cert="Cloudflare Inc ECC CA-3" }; -# { url="https://www.spamhaus.org/drop/edrop.txt"; -# cert="Cloudflare Inc ECC CA-3" }; + 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. Do *NOT* set an empty string, -# that will filter or include everything! +# text. Regular expressions are supported. An empty string has a special +# meaning not to filter or include anything. # These are filters, so excluding messages from forwarding. -:global LogForwardFilter "(debug|info)"; -:global LogForwardFilterMessage []; +:global LogForwardFilter "(debug|info|packet|raw)"; +: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"; @@ -131,6 +154,9 @@ # 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; + # These thresholds control when to send health notification # on temperature and voltage. :global CheckHealthTemperature { @@ -169,7 +195,7 @@ # Run different commands with multiple mode-button presses. :global ModeButton { - 1="/system/script/run leds-toggle-mode;"; + 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;"; @@ -198,16 +224,17 @@ # This is the address used to send gps data to. :global GpsTrackUrl "https://example.com/index.php"; -# Enable this to fetch scripts from given url. -:global ScriptUpdatesFetch true; -:global ScriptUpdatesBaseUrl "https://git.eworm.de/cgit/routeros-scripts/plain/"; +# This is the base url to fetch scripts from. +:global ScriptUpdatesBaseUrl "https://rsc.eworm.de/main/"; # 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 default url (git.eworm.de) +# use next branch with my git url (git.eworm.de) #:global ScriptUpdatesUrlSuffix "?h=next"; # Use this for defaults with $ScriptRunOnce @@ -219,7 +246,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://git.eworm.de/cgit/routeros-scripts/about/#donate +# https://rsc.eworm.de/#donate # Enable this to silence donation hint. :global IDonate false; @@ -237,10 +264,14 @@ "cert2-cn"="4n0th3r-s3cr3t"; }; -# load custom settings from overlay +# load custom settings from overlay and snippets # Warning: Do *NOT* copy this code to overlay! -:do { - /system/script/run global-config-overlay; -} on-error={ - :log error ("Loading configuration from overlay failed!"); +: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!"); + } } diff --git a/global-functions b/global-functions deleted file mode 100644 index ce55b15..0000000 --- a/global-functions +++ /dev/null @@ -1,261 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: global-functions -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md -# -# requires RouterOS, version=7.7 -# -# WARNING: If you find this stripped version of global-functions -# on your Router something went wrong and migration failed. To -# recover run this function: $RouterOSScriptsRecover - -# expected configuration version -:global ExpectedConfigVersion 95; - -# global functions -:global RouterOSScriptsRecover; -:global ScriptInstallUpdate; - -# recover from failed migration -:set RouterOSScriptsRecover do={ - :global ScriptInstallUpdate; - - :foreach Script in={ "global-config"; "global-functions" } do={ - /system/script/set name=$Script source=([ /tool/fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script . ".rsc") output=user as-value]->"data"); - /system/script/run $Script; - } - - $ScriptInstallUpdate; -} - -# install new scripts, update existing scripts -:set ScriptInstallUpdate do={ - :local Scripts [ :toarray $1 ]; - :local NewComment [ :tostr $2 ]; - - :global ExpectedConfigVersion; - :global Identity; - :global IDonate; - :global NoNewsAndChangesNotification; - :global NotificationsWithSymbols; - :global ScriptUpdatesBaseUrl; - :global ScriptUpdatesFetch; - :global ScriptUpdatesUrlSuffix; - - :global CertificateAvailable; - :global EitherOr; - :global Grep; - :global IfThenElse; - :global LogPrintExit2; - :global ParseKeyValueStore; - :global RequiredRouterOS; - :global SendNotification2; - :global SymbolForNotification; - :global ValidateSyntax; - - :if ([ $CertificateAvailable "R3" ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading certificate failed, trying without.") false; - } - - :if ([ $CertificateAvailable "E1" ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading certificate failed, trying without.") false; - } - - :foreach Script in=$Scripts do={ - :if ([ :len [ /system/script/find where name=$Script ] ] = 0) do={ - $LogPrintExit2 info $0 ("Adding new script: " . $Script) false; - /system/script/add name=$Script owner=$Script source="#!rsc by RouterOS\n" comment=$NewComment; - } - } - - :local ExpectedConfigVersionBefore $ExpectedConfigVersion; - :local ReloadGlobalFunctions false; - :local ReloadGlobalConfig false; - - :foreach Script in=[ /system/script/find where source~"^#!rsc by RouterOS\n" ] do={ - :local ScriptVal [ /system/script/get $Script ]; - :local ScriptFile [ /file/find where name=("script-updates/" . $ScriptVal->"name") ]; - :local SourceNew; - :if ([ :len $ScriptFile ] > 0) do={ - :set SourceNew [ /file/get $ScriptFile contents ]; - /file/remove $ScriptFile; - } - - :foreach Scheduler in=[ /system/scheduler/find where on-event~("\\b" . $ScriptVal->"name" . "\\b") ] do={ - :local SchedulerVal [ /system/scheduler/get $Scheduler ]; - :if ($ScriptVal->"policy" != $SchedulerVal->"policy") do={ - $LogPrintExit2 warning $0 ("Policies differ for script '" . $ScriptVal->"name" . \ - "' and its scheduler '" . $SchedulerVal->"name" . "'!") false; - } - } - - :if ([ :len $SourceNew ] = 0 && $ScriptUpdatesFetch = true) do={ - :local Comment [ $ParseKeyValueStore ($ScriptVal->"comment") ]; - :if (!($Comment->"ignore" = true)) do={ - :do { - :local BaseUrl $ScriptUpdatesBaseUrl; - :local UrlSuffix $ScriptUpdatesUrlSuffix; - :if ([ :typeof ($Comment->"base-url") ] = "str") do={ :set BaseUrl ($Comment->"base-url"); } - :if ([ :typeof ($Comment->"url-suffix") ] = "str") do={ :set UrlSuffix ($Comment->"url-suffix"); } - :local Url ($BaseUrl . $ScriptVal->"name" . ".rsc" . $UrlSuffix); - - $LogPrintExit2 debug $0 ("Fetching script '" . $ScriptVal->"name" . "' from url: " . $Url) false; - :local Result [ /tool/fetch check-certificate=yes-without-crl $Url output=user as-value ]; - :if ($Result->"status" = "finished") do={ - :set SourceNew ($Result->"data"); - } - } on-error={ - :if ($ScriptVal->"source" = "#!rsc by RouterOS\n") do={ - $LogPrintExit2 warning $0 ("Failed fetching script '" . $ScriptVal->"name" . \ - "', removing dummy. Typo on installation?") false; - /system/script/remove $Script; - } else={ - $LogPrintExit2 warning $0 ("Failed fetching script '" . $ScriptVal->"name" . "'!") false; - } - } - } - } - - :if ([ :len $SourceNew ] > 0) do={ - :if ($SourceNew != $ScriptVal->"source") do={ - :if ([ :pick $SourceNew 0 18 ] = "#!rsc by RouterOS\n") do={ - :local Required ([ $ParseKeyValueStore [ $Grep $SourceNew "# requires RouterOS, " ] ]->"version"); - :if ([ $RequiredRouterOS $0 [ $EitherOr $Required "0.0" ] false ] = true) do={ - :if ([ $ValidateSyntax $SourceNew ] = true) do={ - $LogPrintExit2 info $0 ("Updating script: " . $ScriptVal->"name") false; - /system/script/set owner=($ScriptVal->"name") source=$SourceNew $Script; - :if ($ScriptVal->"name" = "global-config") do={ - :set ReloadGlobalConfig true; - } - :if ($ScriptVal->"name" = "global-functions" || $ScriptVal->"name" ~ ("^mod/.")) do={ - :set ReloadGlobalFunctions true; - } - } else={ - $LogPrintExit2 warning $0 ("Syntax validation for script '" . $ScriptVal->"name" . \ - "' failed! Ignoring!") false; - } - } else={ - $LogPrintExit2 warning $0 ("The script '" . $ScriptVal->"name" . "' requires RouterOS " . \ - $Required . ", which is not met by your installation. Ignoring!") false; - } - } else={ - $LogPrintExit2 warning $0 ("Looks like new script '" . $ScriptVal->"name" . \ - "' is not valid (missing shebang). Ignoring!") false; - } - } else={ - $LogPrintExit2 debug $0 ("Script '" . $ScriptVal->"name" . "' did not change.") false; - } - } else={ - $LogPrintExit2 debug $0 ("No update for script '" . $ScriptVal->"name" . "'.") false; - } - } - - :if ($ReloadGlobalFunctions = true) do={ - $LogPrintExit2 info $0 ("Reloading global functions.") false; - :do { - /system/script/run global-functions; - } on-error={ - $LogPrintExit2 error $0 ("Reloading global functions failed!") false; - } - } - - :if ($ReloadGlobalConfig = true) do={ - $LogPrintExit2 info $0 ("Reloading global configuration.") false; - :do { - /system/script/run global-config; - } on-error={ - $LogPrintExit2 error $0 ("Reloading global configuration failed!" . \ - " Syntax error or missing overlay?") false; - } - } - - :if ($ExpectedConfigVersionBefore > $ExpectedConfigVersion) do={ - $LogPrintExit2 warning $0 ("The configuration version decreased from " . \ - $ExpectedConfigVersionBefore . " to " . $ExpectedConfigVersion . \ - ". Installed an older version?") false; - } - - :if ($ExpectedConfigVersionBefore < $ExpectedConfigVersion) do={ - :global GlobalConfigChanges; - :global GlobalConfigMigration; - :local ChangeLogCode; - - :do { - :local Url ($ScriptUpdatesBaseUrl . "news-and-changes.rsc" . $ScriptUpdatesUrlSuffix); - $LogPrintExit2 debug $0 ("Fetching news, changes and migration: " . $Url) false; - :local Result [ /tool/fetch check-certificate=yes-without-crl $Url output=user as-value ]; - :if ($Result->"status" = "finished") do={ - :set ChangeLogCode ($Result->"data"); - } - } on-error={ - $LogPrintExit2 warning $0 ("Failed fetching news, changes and migration!") false; - } - - :if ([ :len $ChangeLogCode ] > 0) do={ - :if ([ $ValidateSyntax $ChangeLogCode ] = true) do={ - :do { - [ :parse $ChangeLogCode ]; - } on-error={ - $LogPrintExit2 warning $0 ("The changelog failed to run!") false; - } - } else={ - $LogPrintExit2 warning $0 ("The changelog failed syntax validation!") false; - } - } - - :if ([ :len $GlobalConfigMigration ] > 0) do={ - :for I from=($ExpectedConfigVersionBefore + 1) to=$ExpectedConfigVersion do={ - :local Migration ($GlobalConfigMigration->[ :tostr $I ]); - :if ([ :typeof $Migration ] = "str") do={ - :if ([ $ValidateSyntax $Migration ] = true) do={ - $LogPrintExit2 info $0 ("Applying migration for change " . $I . ": " . $Migration) false; - :do { - [ :parse $Migration ]; - } on-error={ - $LogPrintExit2 warning $0 ("Migration code for change " . $I . " failed to run!") false; - } - } else={ - $LogPrintExit2 warning $0 ("Migration code for change " . $I . " failed syntax validation!") false; - } - } - } - } - - :local NotificationMessage ("The configuration version on " . $Identity . " increased " . \ - "to " . $ExpectedConfigVersion . ", current configuration may need modification. " . \ - "Please review and update global-config-overlay, then re-run global-config."); - $LogPrintExit2 info $0 ($NotificationMessage) false; - - :if ([ :len $GlobalConfigChanges ] > 0) do={ - :set NotificationMessage ($NotificationMessage . "\n\nChanges:"); - :for I from=($ExpectedConfigVersionBefore + 1) to=$ExpectedConfigVersion do={ - :local Change ($GlobalConfigChanges->[ :tostr $I ]); - :set NotificationMessage ($NotificationMessage . "\n " . \ - [ $IfThenElse ($NotificationsWithSymbols = true) ("\E2\97\8F") "*" ] . " " . $Change); - $LogPrintExit2 info $0 ("Change " . $I . ": " . $Change) false; - } - } else={ - :set NotificationMessage ($NotificationMessage . "\n\nNews and changes are not available."); - } - - :if ($NoNewsAndChangesNotification != true) do={ - :local Link; - :if ($IDonate != true) do={ - :set NotificationMessage ($NotificationMessage . \ - "\n\n==== donation hint ====\n" . \ - "This project is developed in private spare time and usage is " . \ - "free of charge for you. If you like the scripts and think this is " . \ - "of value for you or your business please consider a donation."); - :set Link "https://git.eworm.de/cgit/routeros-scripts/about/#donate"; - } - - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "pushpin" ] . "News and configuration changes"); \ - message=$NotificationMessage; link=$Link }); - } - - :set GlobalConfigChanges; - :set GlobalConfigMigration; - } -} diff --git a/global-functions.rsc b/global-functions.rsc index 8c96c72..71aa7de 100644 --- a/global-functions.rsc +++ b/global-functions.rsc @@ -1,35 +1,43 @@ #!rsc by RouterOS # RouterOS script: global-functions -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> # Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# https://rsc.eworm.de/COPYING.md # -# requires RouterOS, version=7.9beta4 +# requires RouterOS, version=7.15 +# requires device-mode, fetch, scheduler # # global functions -# https://git.eworm.de/cgit/routeros-scripts/about/ +# https://rsc.eworm.de/ -:local 0 "global-functions"; +:local ScriptName [ :jobname ]; -# expected configuration version -:global ExpectedConfigVersion 105; +# Git commit id & info, expected configuration version +:global CommitId "unknown"; +:global CommitInfo "unknown"; +:global ExpectedConfigVersion 137; # global variables not to be changed by user :global GlobalFunctionsReady false; -:global FetchUserAgent ("User-Agent: Mikrotik/" . [ /system/resource/get version ] . " Fetch"); :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; @@ -38,21 +46,28 @@ :global GetRandomNumber; :global Grep; :global HexToNum; +:global HumanReadableNum; :global IfThenElse; :global IsDefaultRouteReachable; :global IsDNSResolving; :global IsFullyConnected; :global IsMacLocallyAdministered; :global IsTimeSync; -:global LogPrintExit2; +: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 Read; :global RequiredRouterOS; +:global RmDir; +:global RmFile; :global ScriptFromTerminal; :global ScriptInstallUpdate; :global ScriptLock; @@ -70,23 +85,42 @@ :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 LogPrintExit2; + :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={ - $LogPrintExit2 warning $0 ("This system has low free flash space but " . \ - "is configured to download certificate CRLs to system!") false; + $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={ - $LogPrintExit2 info $0 ("Certificate with CommonName \"" . $CommonName . "\" not available.") false; + $LogPrint info $0 ("Certificate with CommonName '" . $CommonName . "' not available."); :if ([ $CertificateDownload $CommonName ] = false) do={ :return false; } @@ -95,8 +129,8 @@ :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; + $LogPrint info $0 ("Certificate chain for '" . $CommonName . \ + "' is incomplete, missing '" . ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") . "'."); :if ([ $CertificateDownload $CommonName ] = false) do={ :return false; } @@ -110,48 +144,87 @@ :set CertificateDownload do={ :local CommonName [ :tostr $1 ]; - :global FetchUserAgent; :global ScriptUpdatesBaseUrl; :global ScriptUpdatesUrlSuffix; + :global CertificateAvailable; :global CertificateNameByCN; - :global LogPrintExit2; - :global UrlEncode; + :global CleanName; + :global FetchUserAgentStr; + :global LogPrint; + :global RmFile; :global WaitForFile; - $LogPrintExit2 info $0 ("Downloading and importing certificate with " . \ - "CommonName \"" . $CommonName . "\".") false; + $LogPrint info $0 ("Downloading and importing certificate with " . \ + "CommonName '" . $CommonName . "'."); + :local FileName ([ $CleanName $CommonName ] . ".pem"); :do { - :local LocalFileName ($CommonName . ".pem"); - :local UrlFileName ([ $UrlEncode $CommonName ] . ".pem"); - /tool/fetch check-certificate=yes-without-crl http-header-field=$FetchUserAgent \ - ($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 ]; - } + /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={ - $LogPrintExit2 warning $0 ("Failed importing certificate with " . \ - "CommonName \"" . $CommonName . "\"!") false; - :return false; + $LogPrint warning $0 ("Failed downloading certificate with CommonName '" . $CommonName . \ + "' from repository! Trying fallback to mkcert.org..."); + :do { + :if ([ :len [ /certificate/find where common-name="ISRG Root X1" ] ] = 0) do={ + $LogPrint error $0 ("Required certificate is not available."); + :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 ]; + :local Match [ :tostr $1 ]; - :global CharacterReplace; + :global CleanName; + :global LogPrint; + + :local Cert ([ /certificate/find where (common-name=$Match or fingerprint=$Match or name=$Match) ]->0); + :if ([ :len $Cert ] = 0) do={ + $LogPrint warning $0 ("No matching certificate found."); + :return false; + } + :local CommonName [ /certificate/get $Cert common-name ]; + /certificate/set $Cert name=[ $CleanName $CommonName ]; + :return true; +} - :local Cert [ /certificate/find where common-name=$CommonName ]; - /certificate/set $Cert \ - name=[ $CharacterReplace [ $CharacterReplace [ $CharacterReplace $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 @@ -193,34 +266,70 @@ :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 License [ /system/license/get ]; + :local Snmp [ /snmp/get ]; :local Update [ /system/package/update/get ]; :return ( \ [ $FormatLine "Hostname" $Identity ] . "\n" . \ - [ $FormatLine "Board name" ($Resource->"board-name") ] . "\n" . \ - [ $FormatLine "Architecture" ($Resource->"architecture-name") ] . "\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") ] . \ + ([ $FormatLine " Model" ($RouterBoard->"model") ] . \ [ $IfThenElse ([ :len ($RouterBoard->"revision") ] > 0) \ (" " . $RouterBoard->"revision") ] . "\n" . \ - [ $FormatLine "Serial number" ($RouterBoard->"serial-number") ] . "\n") ] . \ - [ $IfThenElse ([ :len ($License->"level") ] > 0) \ - ([ $FormatLine "License" ($License->"level") ] . "\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" && \ @@ -230,16 +339,14 @@ $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={ - :local Input [ :tostr $1 ]; - - :global CharacterReplace; - - :return [ $CharacterReplace $Input ("\r\n") ("\n") ]; + :return [ :tolf [ :tostr $1 ] ]; } # download package from upgrade server @@ -251,8 +358,9 @@ :global CertificateAvailable; :global CleanFilePath; - :global LogPrintExit2; + :global LogPrint; :global MkDir; + :global RmFile; :global WaitForFile; :if ([ :len $PkgName ] = 0) do={ :return false; } @@ -266,22 +374,23 @@ :local PkgDest [ $CleanFilePath ($PkgDir . "/" . $PkgFile) ]; :if ([ $MkDir $PkgDir ] = false) do={ - $LogPrintExit2 warning $0 ("Failed creating directory, not downloading package.") false; + $LogPrint warning $0 ("Failed creating directory, not downloading package."); :return false; } :if ([ :len [ /file/find where name=$PkgDest type="package" ] ] > 0) do={ - $LogPrintExit2 info $0 ("Package file " . $PkgName . " already exists.") false; + $LogPrint info $0 ("Package file " . $PkgName . " already exists."); :return true; } - :if ([ $CertificateAvailable "R3" ] = false) do={ - $LogPrintExit2 error $0 ("Downloading required certificate failed.") 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); - $LogPrintExit2 info $0 ("Downloading package file '" . $PkgName . "'...") false; - $LogPrintExit2 debug $0 ("... from url: " . $Url) false; + $LogPrint info $0 ("Downloading package file '" . $PkgName . "'..."); + $LogPrint debug $0 ("... from url: " . $Url); :local Retry 3; :while ($Retry > 0) do={ :do { @@ -292,14 +401,14 @@ :return true; } } on-error={ - $LogPrintExit2 debug $0 ("Downloading package file failed.") false; + $LogPrint debug $0 ("Downloading package file failed."); } - /file/remove [ find where name=$PkgDest ]; + $RmFile $PkgDest; :set Retry ($Retry - 1); } - $LogPrintExit2 warning $0 ("Downloading package file '" . $PkgName . "' failed.") false; + $LogPrint warning $0 ("Downloading package file '" . $PkgName . "' failed."); :return false; } @@ -313,6 +422,7 @@ :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 ]; } @@ -338,17 +448,96 @@ :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 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={ @@ -383,15 +572,16 @@ :global CertificateAvailable; :global IsMacLocallyAdministered; - :global LogPrintExit2; + :global LogPrint; :if ([ $IsMacLocallyAdministered $Mac ] = true) do={ :return "locally administered"; } :do { - :if ([ $CertificateAvailable "R3" ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true; + :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"); @@ -400,9 +590,9 @@ :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; + $LogPrint debug $0 ("The mac vendor is not known in database."); } on-error={ - $LogPrintExit2 warning $0 ("Failed getting mac vendor.") false; + $LogPrint warning $0 ("Failed getting mac vendor."); } :return "unknown vendor"; } @@ -452,16 +642,48 @@ # 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); + :global HexToNum; + + :if ([ :pick $Input 0 ] = "*") do={ + :return [ $HexToNum [ :pick $Input 1 [ :len $Input ] ] ]; } - :return $Return; + :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) @@ -482,8 +704,6 @@ # check if DNS is resolving :set IsDNSResolving do={ - :global CharacterReplace; - :do { :resolve "low-ttl.eworm.de"; } on-error={ @@ -523,7 +743,7 @@ :global IsTimeSyncCached; :global IsTimeSyncResetNtp; - :global LogPrintExit2; + :global LogPrintOnce; :if ($IsTimeSyncCached = true) do={ :return true; @@ -535,14 +755,15 @@ :return true; } + :local Uptime [ /system/resource/get uptime ]; :if ([ :typeof $IsTimeSyncResetNtp ] = "nothing") do={ - :set IsTimeSyncResetNtp 0s; + :set IsTimeSyncResetNtp $Uptime; } - :local Uptime [ /system/resource/get 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; @@ -552,7 +773,7 @@ :if ([ /system/license/get ]->"level" = "free" || \ [ /system/resource/get ]->"board-name" = "x86") do={ - $LogPrintExit2 debug $0 ("No ntp client configured, relying on RTC for CHR free license and x86.") false; + $LogPrintOnce debug $0 ("No ntp client configured, relying on RTC for CHR free license and x86."); :return true; } @@ -564,16 +785,15 @@ :return false; } - $LogPrintExit2 debug $0 ("No time source configured! Returning gracefully...") false; + $LogPrintOnce debug $0 ("No time source configured! Returning gracefully..."); :return true; } -# log and print with same text, optionally exit -:set LogPrintExit2 do={ +# log and print with same text +:set LogPrint do={ :local Severity [ :tostr $1 ]; :local Name [ :tostr $2 ]; :local Message [ :tostr $3 ]; - :local Exit [ :tostr $4 ]; :global PrintDebug; :global PrintDebugOverride; @@ -606,37 +826,80 @@ :if ($Severity != "debug" || $Debug = true) do={ :put ([ $PrintSeverity $Severity ] . ": " . $Message); } +} - :if ($Exit = "true") do={ - :error ("Hard error to exit."); +# 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 CharacterReplace; :global CleanFilePath; - :global GetRandom20CharAlNum; - :global LogPrintExit2; + :global LogPrint; + :global RmDir; :global WaitForFile; :local MkTmpfs do={ - :global LogPrintExit2; + :global LogPrint; :global WaitForFile; - :if ([ :len [ /disk/find where slot=tmpfs type=tmpfs ] ] = 1) do={ + :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; } - $LogPrintExit2 info $0 ("Creating disk of type tmpfs.") false; - /file/remove [ find where name="tmpfs" type="directory" ]; + $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={ - $LogPrintExit2 warning $0 ("Creating disk of type tmpfs failed!") false; + $LogPrint warning $0 ("Creating disk of type tmpfs failed!"); :return false; } :return true; @@ -648,7 +911,10 @@ :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; } @@ -659,12 +925,10 @@ } :do { - :local File ($Path . "/file"); - /file/add name=$File; - $WaitForFile $File; - /file/remove $File; + /file/add type="directory" name=$Path; + $WaitForFile $Path; } on-error={ - $LogPrintExit2 warning $0 ("Making directory '" . $Path . "' failed!") false; + $LogPrint warning $0 ("Making directory '" . $Path . "' failed!"); :return false; } @@ -680,15 +944,6 @@ :set ParseDate do={ :local Date [ :tostr $1 ]; - :if ([ :pick $Date 4 5 ] != "-") do={ - :local Months { "jan"=1; "feb"=2; "mar"=3; "apr"=4; "may"=5; "jun"=6; - "jul"=7; "aug"=8; "sep"=9; "oct"=10; "nov"=11; "dec"=12 }; - - :return ({ "year"=[ :tonum [ :pick $Date 7 11 ] ]; - "month"=($Months->[ :pick $Date 0 3 ]); - "day"=[ :tonum [ :pick $Date 4 6 ] ] }); - } - :return ({ "year"=[ :tonum [ :pick $Date 0 4 ] ]; "month"=[ :tonum [ :pick $Date 5 7 ] ]; "day"=[ :tonum [ :pick $Date 8 10 ] ] }); @@ -697,14 +952,24 @@ # 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={ - :set ($Result->[ :pick $KeyValue 0 [ :find $KeyValue "=" ] ]) \ - [ :pick $KeyValue ([ :find $KeyValue "=" ] + 1) [ :len $KeyValue ] ]; + :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; } @@ -714,24 +979,34 @@ # print lines with trailing carriage return :set PrettyPrint do={ - :local Input [ :tostr $1 ]; + :put [ :tocrlf [ :tostr $1 ] ]; +} - :global Unix2Dos; +# strip protocol from from url string +:set ProtocolStrip do={ + :local Input [ :tostr $1 ]; - :put [ $Unix2Dos $Input ]; + :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; - :delay ([ $GetRandomNumber $1 ] . [ $EitherOr $2 "s" ]); -} + :if ($Time = 0) do={ + :return false; + } -# read input from user -:set Read do={ - :return; + :delay ([ $MAX 10 [ $GetRandomNumber ([ :tonsec [ :totime ($Time . [ $EitherOr $Unit "s" ]) ] ] / 1000000) ] ] . "ms"); } # check for required RouterOS version @@ -741,29 +1016,90 @@ :local Warn [ :tostr $3 ]; :global IfThenElse; - :global LogPrintExit2; + :global LogPrint; :global VersionToNum; :if (!($Required ~ "^\\d+\\.\\d+((alpha|beta|rc|\\.)\\d+|)\$")) do={ - $LogPrintExit2 error $0 ("No valid RouterOS version: " . $Required) false; + $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={ - $LogPrintExit2 warning $0 ("This " . [ $IfThenElse ([ :pick $Caller 0 ] = ("\$")) "function" "script" ] . \ - " '" . $Caller . "' (at least specific functionality) requires RouterOS " . $Required . ". Please update!") false; + $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 LogPrintExit2; + :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 ]; @@ -771,162 +1107,202 @@ :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; + $LogPrint debug $0 ("Script " . $Script . " started from terminal."); :return true; } } - $LogPrintExit2 debug $0 ("Script " . $Script . " NOT started from terminal.") false; + $LogPrint debug $0 ("Script " . $Script . " NOT started from terminal."); :return false; } # install new scripts, update existing scripts -:set ScriptInstallUpdate do={ +:set ScriptInstallUpdate do={ :do { :local Scripts [ :toarray $1 ]; :local NewComment [ :tostr $2 ]; + :global CommitId; + :global CommitInfo; :global ExpectedConfigVersion; - :global FetchUserAgent; :global Identity; :global IDonate; :global NoNewsAndChangesNotification; - :global NotificationsWithSymbols; :global ScriptUpdatesBaseUrl; - :global ScriptUpdatesFetch; + :global ScriptUpdatesCRLF; :global ScriptUpdatesUrlSuffix; :global CertificateAvailable; :global EitherOr; + :global FetchUserAgentStr; :global Grep; :global IfThenElse; - :global LogPrintExit2; + :global LogPrint; + :global LogPrintOnce; :global ParseKeyValueStore; :global RequiredRouterOS; :global SendNotification2; :global SymbolForNotification; :global ValidateSyntax; - :if ([ $CertificateAvailable "R3" ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading certificate failed, trying without.") false; - } - - :if ([ $CertificateAvailable "E1" ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading certificate failed, trying without.") false; + :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={ - $LogPrintExit2 info $0 ("Adding new script: " . $Script) false; + $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 ScriptFile [ /file/find where name=("script-updates/" . $ScriptVal->"name") . ".rsc" ]; + :local ScriptInfo [ $ParseKeyValueStore ($ScriptVal->"comment") ]; :local SourceNew; - :if ([ :len $ScriptFile ] > 0) do={ - :set SourceNew [ /file/get $ScriptFile contents ]; - /file/remove $ScriptFile; - } :foreach Scheduler in=[ /system/scheduler/find where on-event~("\\b" . $ScriptVal->"name" . "\\b") ] do={ :local SchedulerVal [ /system/scheduler/get $Scheduler ]; :if ($ScriptVal->"policy" != $SchedulerVal->"policy") do={ - $LogPrintExit2 warning $0 ("Policies differ for script '" . $ScriptVal->"name" . \ - "' and its scheduler '" . $SchedulerVal->"name" . "'!") false; + $LogPrint warning $0 ("Policies differ for script '" . $ScriptVal->"name" . \ + "' and its scheduler '" . $SchedulerVal->"name" . "'!"); } } - :if ([ :len $SourceNew ] = 0 && $ScriptUpdatesFetch = true) do={ - :local Comment [ $ParseKeyValueStore ($ScriptVal->"comment") ]; - :if (!($Comment->"ignore" = true)) do={ - :do { - :local BaseUrl $ScriptUpdatesBaseUrl; - :local UrlSuffix $ScriptUpdatesUrlSuffix; - :if ([ :typeof ($Comment->"base-url") ] = "str") do={ :set BaseUrl ($Comment->"base-url"); } - :if ([ :typeof ($Comment->"url-suffix") ] = "str") do={ :set UrlSuffix ($Comment->"url-suffix"); } - :local Url ($BaseUrl . $ScriptVal->"name" . ".rsc" . $UrlSuffix); - - $LogPrintExit2 debug $0 ("Fetching script '" . $ScriptVal->"name" . "' from url: " . $Url) false; - :local Result [ /tool/fetch check-certificate=yes-without-crl http-header-field=$FetchUserAgent \ - $Url output=user as-value ]; - :if ($Result->"status" = "finished") do={ - :set SourceNew ($Result->"data"); - } - } on-error={ - :if ($ScriptVal->"source" = "#!rsc by RouterOS\n") do={ - $LogPrintExit2 warning $0 ("Failed fetching script '" . $ScriptVal->"name" . \ - "', removing dummy. Typo on installation?") false; - /system/script/remove $Script; - } else={ - $LogPrintExit2 warning $0 ("Failed fetching script '" . $ScriptVal->"name" . "'!") false; - } + :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; + } + + :if ([ :len ($ScriptInfo->"certificate") ] > 0) do={ + :if ([ $CertificateAvailable ($ScriptInfo->"certificate") ] = false) do={ + $LogPrint warning $0 ("Downloading certificate failed, trying without."); } } - } - :if ([ :len $SourceNew ] > 0) do={ - :if ($SourceNew != $ScriptVal->"source") do={ - :if ([ :pick $SourceNew 0 18 ] = "#!rsc by RouterOS\n") do={ - :local Required ([ $ParseKeyValueStore [ $Grep $SourceNew ("\23 requires RouterOS, ") ] ]->"version"); - :if ([ $RequiredRouterOS $0 [ $EitherOr $Required "0.0" ] false ] = true) do={ - :if ([ $ValidateSyntax $SourceNew ] = true) do={ - $LogPrintExit2 info $0 ("Updating script: " . $ScriptVal->"name") false; - /system/script/set owner=($ScriptVal->"name") source=$SourceNew $Script; - :if ($ScriptVal->"name" = "global-config") do={ - :set ReloadGlobalConfig true; - } - :if ($ScriptVal->"name" = "global-functions" || $ScriptVal->"name" ~ ("^mod/.")) do={ - :set ReloadGlobalFunctions true; - } - } else={ - $LogPrintExit2 warning $0 ("Syntax validation for script '" . $ScriptVal->"name" . \ - "' failed! Ignoring!") false; - } - } else={ - $LogPrintExit2 warning $0 ("The script '" . $ScriptVal->"name" . "' requires RouterOS " . \ - $Required . ", which is not met by your installation. Ignoring!") false; - } + :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={ - $LogPrintExit2 warning $0 ("Looks like new script '" . $ScriptVal->"name" . \ - "' is not valid (missing shebang). Ignoring!") false; + $LogPrint warning $0 ("Failed fetching script '" . $ScriptVal->"name" . "'!"); } - } else={ - $LogPrintExit2 debug $0 ("Script '" . $ScriptVal->"name" . "' did not change.") false; + :error false; } - } else={ - $LogPrintExit2 debug $0 ("No update for script '" . $ScriptVal->"name" . "'.") 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={ - $LogPrintExit2 info $0 ("Reloading global functions.") false; + $LogPrint info $0 ("Reloading global functions."); :do { /system/script/run global-functions; } on-error={ - $LogPrintExit2 error $0 ("Reloading global functions failed!") false; + $LogPrint error $0 ("Reloading global functions failed!"); } } :if ($ReloadGlobalConfig = true) do={ - $LogPrintExit2 info $0 ("Reloading global configuration.") false; + $LogPrint info $0 ("Reloading global configuration."); :do { /system/script/run global-config; } on-error={ - $LogPrintExit2 error $0 ("Reloading global configuration failed!" . \ - " Syntax error or missing overlay?") false; + $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={ - $LogPrintExit2 warning $0 ("The configuration version decreased from " . \ + $LogPrint warning $0 ("The configuration version decreased from " . \ $ExpectedConfigVersionBefore . " to " . $ExpectedConfigVersion . \ - ". Installed an older version?") false; + ". Installed an older version?"); } :if ($ExpectedConfigVersionBefore < $ExpectedConfigVersion) do={ @@ -936,14 +1312,14 @@ :do { :local Url ($ScriptUpdatesBaseUrl . "news-and-changes.rsc" . $ScriptUpdatesUrlSuffix); - $LogPrintExit2 debug $0 ("Fetching news, changes and migration: " . $Url) false; - :local Result [ /tool/fetch check-certificate=yes-without-crl http-header-field=$FetchUserAgent \ - $Url output=user as-value ]; + $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={ - $LogPrintExit2 warning $0 ("Failed fetching news, changes and migration!") false; + $LogPrint warning $0 ("Failed fetching news, changes and migration!"); } :if ([ :len $ChangeLogCode ] > 0) do={ @@ -951,43 +1327,49 @@ :do { [ :parse $ChangeLogCode ]; } on-error={ - $LogPrintExit2 warning $0 ("The changelog failed to run!") false; + $LogPrint warning $0 ("The changelog failed to run!"); } } else={ - $LogPrintExit2 warning $0 ("The changelog failed syntax validation!") false; + $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 ]); - :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; + :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."); - $LogPrintExit2 info $0 ($NotificationMessage) false; + $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 " . \ - [ $IfThenElse ($NotificationsWithSymbols = true) ("\E2\97\8F") "*" ] . " " . $Change); - $LogPrintExit2 info $0 ("Change " . $I . ": " . $Change) false; + [ $SymbolForNotification "pushpin" "*" ] . $Change); + $LogPrint info $0 ("Change " . $I . ": " . $Change); } } else={ :set NotificationMessage ($NotificationMessage . "\n\nNews and changes are not available."); @@ -1001,7 +1383,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."); - :set Link "https://git.eworm.de/cgit/routeros-scripts/about/#donate"; + :set Link "https://rsc.eworm.de/#donate"; } $SendNotification2 ({ origin=$0; \ @@ -1012,17 +1394,18 @@ :set GlobalConfigChanges; :set GlobalConfigMigration; } -} +} on-error={ + :global ExitError; $ExitError false $0; +} } # lock script against multiple invocation :set ScriptLock do={ - :local Script [ :tostr $1 ]; - :local DoReturn $2; - :local WaitMax ([ :tonum $3 ] * 10); + :local Script [ :tostr $1 ]; + :local WaitMax [ :totime $2 ]; :global GetRandom20CharAlNum; :global IfThenElse; - :global LogPrintExit2; + :global LogPrint; :global ScriptLockOrder; :if ([ :typeof $ScriptLockOrder ] = "nothing") do={ @@ -1107,16 +1490,22 @@ :set ($ScriptLockOrder->$Script) ({}); } + :if ([ :typeof $WaitMax ] = "nil" ) do={ + :set WaitMax 0s; + } + :if ([ :len [ /system/script/find where name=$Script ] ] = 0) do={ - $LogPrintExit2 error $0 ("A script named '" . $Script . "' does not exist!") true; + $LogPrint error $0 ("A script named '" . $Script . "' does not exist!"); + :error false; } :if ([ $JobCount $Script ] = 0) do={ - $LogPrintExit2 error $0 ("No script '" . $Script . "' is running!") true; + $LogPrint error $0 ("No script '" . $Script . "' is running!"); + :error false; } :if ([ $TicketCount $Script ] >= [ $JobCount $Script ]) do={ - $LogPrintExit2 error $0 ("More tickets than running scripts '" . $Script . "', resetting!") false; + $LogPrint error $0 ("More tickets than running scripts '" . $Script . "', resetting!"); :set ($ScriptLockOrder->$Script) ({}); /system/script/job/remove [ find where script=$Script ]; } @@ -1124,30 +1513,36 @@ :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; + :local WaitInterval ($WaitMax / 20); + :local WaitTime $WaitMax; + :while ($WaitTime > 0 && \ + ([ $IsFirstTicket $Script $MyTicket ] = false || \ + [ $TicketCount $Script ] < [ $JobCount $Script ])) do={ + :set WaitTime ($WaitTime - $WaitInterval); + :delay $WaitInterval; } - :if ([ $IsFirstTicket $Script $MyTicket ] = true && [ $TicketCount $Script ] = [ $JobCount $Script ]) do={ + :if ([ $IsFirstTicket $Script $MyTicket ] = true && \ + [ $TicketCount $Script ] = [ $JobCount $Script ]) do={ $RemoveTicket $Script $MyTicket; $CleanupTickets $Script; - :return false; + :return true; } $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; + $LogPrint debug $0 ("Script '" . $Script . "' started more than once" . \ + [ $IfThenElse ($WaitTime < $WaitMax) " and timed out waiting for lock" "" ] . "..."); + :return false; } # send notification via NotificationFunctions - expects at least two string arguments -:set SendNotification do={ +:set SendNotification do={ :do { :global SendNotification2; - $SendNotification2 ({ subject=$1; message=$2; link=$3; silent=$4 }); -} + $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={ @@ -1164,9 +1559,15 @@ # 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"; @@ -1176,31 +1577,45 @@ "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"; - "up-arrow"="\E2\AC\86"; + "star"="\E2\AD\90"; "warning-sign"="\E2\9A\A0"; "white-heavy-check-mark"="\E2\9C\85" } - :return (($Symbols->$1) . "\EF\B8\8F"); + :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 ""; + :return [ $IfThenElse ([ :len $2 ] > 0) ([ :tostr $2 ] . " ") "" ]; } :local Return ""; :foreach Symbol in=[ :toarray $1 ] do={ @@ -1211,12 +1626,7 @@ # convert line endings, UNIX -> DOS :set Unix2Dos do={ - :local Input [ :tostr $1 ]; - - :global CharacterReplace; - - :return [ $CharacterReplace [ $CharacterReplace $Input \ - ("\n") ("\r\n") ] ("\r\r\n") ("\r\n") ]; + :return [ :tocrlf [ :tostr $1 ] ]; } # url encoding @@ -1267,7 +1677,7 @@ :global CharacterReplace; :set Input [ $CharacterReplace $Input "." "," ]; - :foreach I in={ "alpha"; "beta"; "rc" } do={ + :foreach I in={ "zero"; "alpha"; "beta"; "rc" } do={ :set Input [ $CharacterReplace $Input $I ("," . $I . ",") ]; } @@ -1278,6 +1688,7 @@ :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); } @@ -1315,19 +1726,30 @@ :global CleanFilePath; :global EitherOr; + :global MAX; :set FileName [ $CleanFilePath $FileName ]; :local I 1; - :local Delay ([ :totime [ $EitherOr $WaitTime 2s ] ] / 20); + :local Delay ([ $MAX [ $EitherOr $WaitTime 2s ] 100ms ] / 10); :while ([ :len [ /file/find where name=$FileName ] ] = 0) do={ - :if ($I >= 20) do={ + :if ($I >= 10) do={ :return false; } :delay $Delay; :set I ($I + 1); } - :return true; + + :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) @@ -1357,12 +1779,17 @@ :do { /system/script/run $Script; } on-error={ - $LogPrintExit2 error $0 ("Module '" . $ScriptVal->"name" . "' failed to run.") false; + $LogPrint error $0 ("Module '" . $ScriptVal->"name" . "' failed to run."); } } else={ - $LogPrintExit2 error $0 ("Module '" . $ScriptVal->"name" . "' failed syntax validation, skipping.") false; + $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.rsc b/global-wait.rsc index fe1928b..ca3fc0c 100644 --- a/global-wait.rsc +++ b/global-wait.rsc @@ -1,11 +1,12 @@ #!rsc by RouterOS # RouterOS script: global-wait -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2020-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # wait for global-functions to finish -# https://git.eworm.de/cgit/routeros-scripts/about/doc/global-wait.md +# https://rsc.eworm.de/doc/global-wait.md -:local 0 "global-wait"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } diff --git a/gps-track.rsc b/gps-track.rsc index 0d328d5..dea56d2 100644 --- a/gps-track.rsc +++ b/gps-track.rsc @@ -1,43 +1,53 @@ #!rsc by RouterOS # RouterOS script: gps-track -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> +# 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://git.eworm.de/cgit/routeros-scripts/about/doc/gps-track.md +# https://rsc.eworm.de/doc/gps-track.md -:local 0 "gps-track"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global GpsTrackUrl; -:global Identity; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; + + :global GpsTrackUrl; + :global Identity; -:global LogPrintExit2; -:global ScriptLock; -:global WaitFullyConnected; + :global FetchUserAgentStr; + :global LogPrint; + :global ScriptLock; + :global WaitFullyConnected; -$ScriptLock $0; -$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 ]; + :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 $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; - } on-error={ - $LogPrintExit2 warning $0 ("Failed sending GPS data!") false; + :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."); } -} else={ - $LogPrintExit2 debug $0 ("GPS data not valid.") false; +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/hotspot-to-wpa-cleanup.capsman.rsc b/hotspot-to-wpa-cleanup.capsman.rsc index b9a6620..033d0e7 100644 --- a/hotspot-to-wpa-cleanup.capsman.rsc +++ b/hotspot-to-wpa-cleanup.capsman.rsc @@ -1,68 +1,80 @@ #!rsc by RouterOS # RouterOS script: hotspot-to-wpa-cleanup.capsman -# Copyright (c) 2021-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2021-2025 Christian Hesse <mail@eworm.de> +# 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://git.eworm.de/cgit/routeros-scripts/about/doc/hotspot-to-wpa.md +# https://rsc.eworm.de/doc/hotspot-to-wpa.md # # !! Do not edit this file, it is generated from template! -:local 0 "hotspot-to-wpa-cleanup.capsman"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global EitherOr; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$ScriptLock $0 false 10; + :global EitherOr; + :global LogPrint; + :global ParseKeyValueStore; + :global ScriptLock; -: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 ] ]; + :if ([ $ScriptLock $ScriptName 10 ] = false) do={ + :set ExitOK true; + :error false; } -} -: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={ - $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; + :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/access-list/find where comment~"^hotspot-to-wpa:" \ - !(comment~[ /system/clock/get date ]) ] 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={ - $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 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 Server,Timeout in=$DHCPServers do={ - :foreach Lease in=[ /ip/dhcp-server/lease/find where !dynamic status="waiting" \ - server=$Server last-seen>$Timeout 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 " . ($LeaseVal->"last-seen") . ", removing.") false; - /caps-man/access-list/remove [ find where comment~"^hotspot-to-wpa:" \ - mac-address=($LeaseVal->"mac-address") ]; - /ip/dhcp-server/lease/remove $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 index 8cc732b..0f8c490 100644 --- a/hotspot-to-wpa-cleanup.template.rsc +++ b/hotspot-to-wpa-cleanup.template.rsc @@ -1,78 +1,87 @@ #!rsc by RouterOS # RouterOS script: hotspot-to-wpa-cleanup%TEMPL% -# Copyright (c) 2021-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2021-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md # # provides: lease-script, order=80 -# NOT /caps-man # -# requires RouterOS, version=7.12beta3 -# NOT /caps-man # +# requires RouterOS, version=7.15 +# requires device-mode, hotspot # # manage and clean up private WPA passphrase after hotspot login -# https://git.eworm.de/cgit/routeros-scripts/about/doc/hotspot-to-wpa.md +# 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. -:local 0 "hotspot-to-wpa-cleanup%TEMPL%"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global EitherOr; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$ScriptLock $0 false 10; + :global EitherOr; + :global LogPrint; + :global ParseKeyValueStore; + :global ScriptLock; -: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 ] ]; + :if ([ $ScriptLock $ScriptName 10 ] = false) do={ + :set ExitOK true; + :error false; } -} -:foreach Client in=[ /caps-man/registration-table/find where comment~"^hotspot-to-wpa:" ] do={ -:foreach Client in=[ /interface/wifiwave2/registration-table/find where comment~"^hotspot-to-wpa:" ] do={ - :local ClientVal [ /caps-man/registration-table/get $Client ]; - :local ClientVal [ /interface/wifiwave2/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={ - $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; + :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/access-list/find where comment~"^hotspot-to-wpa:" \ -:foreach Client in=[ /interface/wifiwave2/access-list/find where comment~"^hotspot-to-wpa:" \ - !(comment~[ /system/clock/get date ]) ] do={ - :local ClientVal [ /caps-man/access-list/get $Client ]; - :local ClientVal [ /interface/wifiwave2/access-list/get $Client ]; - :if ([ :len [ /ip/dhcp-server/lease/find where !dynamic comment~"^hotspot-to-wpa:" \ - 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; - /interface/wifiwave2/access-list/remove $Client; + :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 Server,Timeout in=$DHCPServers do={ - :foreach Lease in=[ /ip/dhcp-server/lease/find where !dynamic status="waiting" \ - server=$Server last-seen>$Timeout 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 " . ($LeaseVal->"last-seen") . ", removing.") false; - /caps-man/access-list/remove [ find where comment~"^hotspot-to-wpa:" \ - /interface/wifiwave2/access-list/remove [ find where comment~"^hotspot-to-wpa:" \ - mac-address=($LeaseVal->"mac-address") ]; - /ip/dhcp-server/lease/remove $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 new file mode 100644 index 0000000..dfec697 --- /dev/null +++ b/hotspot-to-wpa-cleanup.wifi.rsc @@ -0,0 +1,80 @@ +#!rsc by RouterOS +# RouterOS script: hotspot-to-wpa-cleanup.wifi +# Copyright (c) 2021-2025 Christian Hesse <mail@eworm.de> +# 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-cleanup.wifiwave2.rsc b/hotspot-to-wpa-cleanup.wifiwave2.rsc deleted file mode 100644 index e316c17..0000000 --- a/hotspot-to-wpa-cleanup.wifiwave2.rsc +++ /dev/null @@ -1,69 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: hotspot-to-wpa-cleanup.wifiwave2 -# Copyright (c) 2021-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md -# -# provides: lease-script, order=80 -# requires RouterOS, version=7.12beta3 -# -# manage and clean up private WPA passphrase after hotspot login -# https://git.eworm.de/cgit/routeros-scripts/about/doc/hotspot-to-wpa.md -# -# !! Do not edit this file, it is generated from template! - -:local 0 "hotspot-to-wpa-cleanup.wifiwave2"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global EitherOr; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; - -$ScriptLock $0 false 10; - -: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/wifiwave2/registration-table/find where comment~"^hotspot-to-wpa:" ] do={ - :local ClientVal [ /interface/wifiwave2/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={ - $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=[ /interface/wifiwave2/access-list/find where comment~"^hotspot-to-wpa:" \ - !(comment~[ /system/clock/get date ]) ] do={ - :local ClientVal [ /interface/wifiwave2/access-list/get $Client ]; - :if ([ :len [ /ip/dhcp-server/lease/find where !dynamic comment~"^hotspot-to-wpa:" \ - 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; - /interface/wifiwave2/access-list/remove $Client; - } -} - -:foreach Server,Timeout in=$DHCPServers do={ - :foreach Lease in=[ /ip/dhcp-server/lease/find where !dynamic status="waiting" \ - server=$Server last-seen>$Timeout 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 " . ($LeaseVal->"last-seen") . ", removing.") false; - /interface/wifiwave2/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.capsman.rsc b/hotspot-to-wpa.capsman.rsc index a7ebafb..3f51475 100644 --- a/hotspot-to-wpa.capsman.rsc +++ b/hotspot-to-wpa.capsman.rsc @@ -1,89 +1,105 @@ #!rsc by RouterOS # RouterOS script: hotspot-to-wpa.capsman -# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2019-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 +# requires device-mode, hotspot # # add private WPA passphrase after hotspot login -# https://git.eworm.de/cgit/routeros-scripts/about/doc/hotspot-to-wpa.md +# https://rsc.eworm.de/doc/hotspot-to-wpa.md # # !! Do not edit this file, it is generated from template! -:local 0 "hotspot-to-wpa.capsman"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global EitherOr; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$ScriptLock $0; + :global EitherOr; + :global LogPrint; + :global ParseKeyValueStore; + :global ScriptLock; -:local MacAddress $"mac-address"; -:local UserName $username; + :local MacAddress $"mac-address"; + :local UserName $username; -:if ([ :typeof $MacAddress ] = "nothing" || [ :typeof $UserName ] = "nothing") do={ - $LogPrintExit2 error $0 ("This script is supposed to run from hotspot on login.") true; -} + :if ([ $ScriptLock $ScriptName ] = false) do={ + :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 ([ :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; + } -: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); + :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 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 ([ :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 ($Template->"action" = "reject") do={ - $LogPrintExit2 info $0 ("Ignoring login for hotspot '" . $Hotspot . "'.") true; -} + :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) ]; -# allow login page to load -:delay 1s; + :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; -$LogPrintExit2 info $0 ("Adding/updating access-list entry for mac address " . $MacAddress . \ - " (user " . $UserName . ").") false; -/caps-man/access-list/remove [ find where mac-address=$MacAddress comment~"^hotspot-to-wpa: " ]; -/caps-man/access-list/add private-passphrase=($UserVal->"password") ssid-regexp="-wpa\$" \ - mac-address=$MacAddress comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) \ - action=reject place-before=$PlaceBefore; + $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 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; } -} -: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; + :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 index f465722..068241d 100644 --- a/hotspot-to-wpa.template.rsc +++ b/hotspot-to-wpa.template.rsc @@ -1,110 +1,125 @@ #!rsc by RouterOS # RouterOS script: hotspot-to-wpa%TEMPL% -# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2019-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 +# requires device-mode, hotspot # # add private WPA passphrase after hotspot login -# https://git.eworm.de/cgit/routeros-scripts/about/doc/hotspot-to-wpa.md +# 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. -:local 0 "hotspot-to-wpa%TEMPL%"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global EitherOr; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$ScriptLock $0; + :global EitherOr; + :global LogPrint; + :global ParseKeyValueStore; + :global ScriptLock; -:local MacAddress $"mac-address"; -:local UserName $username; + :local MacAddress $"mac-address"; + :local UserName $username; -:if ([ :typeof $MacAddress ] = "nothing" || [ :typeof $UserName ] = "nothing") do={ - $LogPrintExit2 error $0 ("This script is supposed to run from hotspot on login.") true; -} + :if ([ $ScriptLock $ScriptName ] = false) do={ + :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 ([ :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; + } -:if ([ :len [ /caps-man/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ] ] = 0) do={ -:if ([ :len [ /interface/wifiwave2/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/wifiwave2/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); -:local PlaceBefore ([ /interface/wifiwave2/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ]->0); + :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 \ -:if ([ :len [ /interface/wifiwave2/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/wifiwave2/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 \ -:local Template [ /interface/wifiwave2/access-list/get ([ find where \ - comment=("hotspot-to-wpa template " . $Hotspot) disabled ]->0) ]; + :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 ($Template->"action" = "reject") do={ - $LogPrintExit2 info $0 ("Ignoring login for hotspot '" . $Hotspot . "'.") true; -} + :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) ]; -# allow login page to load -:delay 1s; + :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; -$LogPrintExit2 info $0 ("Adding/updating access-list entry for mac address " . $MacAddress . \ - " (user " . $UserName . ").") false; -/caps-man/access-list/remove [ find where mac-address=$MacAddress comment~"^hotspot-to-wpa: " ]; -/interface/wifiwave2/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/wifiwave2/access-list/add passphrase=($UserVal->"password") ssid-regexp="-wpa\$" \ - mac-address=$MacAddress comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) \ - action=reject place-before=$PlaceBefore; + $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/wifiwave2/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/wifiwave2/access-list/set $Entry !passphrase; - } else={ - /caps-man/access-list/set $Entry private-passphrase=$PrivatePassphrase; - /interface/wifiwave2/access-list/set $Entry passphrase=$PrivatePassphrase; + :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/wifiwave2/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/wifiwave2/access-list/set $Entry vlan-id=$VlanId; -} -# NOT /interface/wifiwave2 # -:local VlanMode [ $EitherOr ($UserInfo->"vlan-mode") ($Template->"vlan-mode") ]; -:if ([ :len $VlanMode] > 0) do={ - /caps-man/access-list/set $Entry vlan-mode=$VlanMode; - /interface/wifiwave2/access-list/set $Entry vlan-mode=$VlanMode; -} -# NOT /interface/wifiwave2 # + :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/wifiwave2/access-list/set $Entry action=accept; + :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 new file mode 100644 index 0000000..cc5e2fc --- /dev/null +++ b/hotspot-to-wpa.wifi.rsc @@ -0,0 +1,102 @@ +#!rsc by RouterOS +# RouterOS script: hotspot-to-wpa.wifi +# Copyright (c) 2019-2025 Christian Hesse <mail@eworm.de> +# 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/hotspot-to-wpa.wifiwave2.rsc b/hotspot-to-wpa.wifiwave2.rsc deleted file mode 100644 index ea44a9d..0000000 --- a/hotspot-to-wpa.wifiwave2.rsc +++ /dev/null @@ -1,86 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: hotspot-to-wpa.wifiwave2 -# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md -# -# add private WPA passphrase after hotspot login -# https://git.eworm.de/cgit/routeros-scripts/about/doc/hotspot-to-wpa.md -# -# !! Do not edit this file, it is generated from template! - -:local 0 "hotspot-to-wpa.wifiwave2"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global EitherOr; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; - -$ScriptLock $0; - -:local MacAddress $"mac-address"; -:local UserName $username; - -:if ([ :typeof $MacAddress ] = "nothing" || [ :typeof $UserName ] = "nothing") do={ - $LogPrintExit2 error $0 ("This script is supposed to run from hotspot on login.") true; -} - -: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/wifiwave2/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ] ] = 0) do={ - /interface/wifiwave2/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 ([ /interface/wifiwave2/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ]->0); - -:if ([ :len [ /interface/wifiwave2/access-list/find where \ - comment=("hotspot-to-wpa template " . $Hotspot) disabled ] ] = 0) do={ - /interface/wifiwave2/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 [ /interface/wifiwave2/access-list/get ([ find where \ - comment=("hotspot-to-wpa template " . $Hotspot) disabled ]->0) ]; - -:if ($Template->"action" = "reject") do={ - $LogPrintExit2 info $0 ("Ignoring login for hotspot '" . $Hotspot . "'.") true; -} - -# allow login page to load -:delay 1s; - -$LogPrintExit2 info $0 ("Adding/updating access-list entry for mac address " . $MacAddress . \ - " (user " . $UserName . ").") false; -/interface/wifiwave2/access-list/remove [ find where mac-address=$MacAddress comment~"^hotspot-to-wpa: " ]; -/interface/wifiwave2/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/wifiwave2/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/wifiwave2/access-list/set $Entry !passphrase; - } else={ - /interface/wifiwave2/access-list/set $Entry passphrase=$PrivatePassphrase; - } -} -:local SsidRegexp [ $EitherOr ($UserInfo->"ssid-regexp") ($Template->"ssid-regexp") ]; -:if ([ :len $SsidRegexp ] > 0) do={ - /interface/wifiwave2/access-list/set $Entry ssid-regexp=$SsidRegexp; -} -:local VlanId [ $EitherOr ($UserInfo->"vlan-id") ($Template->"vlan-id") ]; -:if ([ :len $VlanId ] > 0) do={ - /interface/wifiwave2/access-list/set $Entry vlan-id=$VlanId; -} - -:delay 2s; -/interface/wifiwave2/access-list/set $Entry action=accept; diff --git a/ip-addr-bridge.rsc b/ip-addr-bridge.rsc index 99fcba5..68ff4a4 100644 --- a/ip-addr-bridge.rsc +++ b/ip-addr-bridge.rsc @@ -1,10 +1,10 @@ #!rsc by RouterOS # RouterOS script: ip-addr-bridge -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/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 +# https://rsc.eworm.de/doc/ip-addr-bridge.md :foreach Bridge in=[ /interface/bridge/find ] do={ :local BrName [ /interface/bridge/get $Bridge name ]; diff --git a/ipsec-to-dns.rsc b/ipsec-to-dns.rsc index d961865..26dab0a 100644 --- a/ipsec-to-dns.rsc +++ b/ipsec-to-dns.rsc @@ -1,72 +1,84 @@ #!rsc by RouterOS # RouterOS script: ipsec-to-dns -# Copyright (c) 2021-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2021-2025 Christian Hesse <mail@eworm.de> +# 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://git.eworm.de/cgit/routeros-scripts/about/doc/ipsec-to-dns.md +# https://rsc.eworm.de/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; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:global CharacterReplace; -:global EscapeForRegEx; -:global IfThenElse; -:global LogPrintExit2; -:global ScriptLock; + :global Domain; + :global HostNameInZone; + :global Identity; + :global PrefixInZone; -$ScriptLock $0; + :global CharacterReplace; + :global EscapeForRegEx; + :global IfThenElse; + :global LogPrint; + :global ScriptLock; -: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 ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } -: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; - $LogPrintExit2 warning $0 ("Added disabled static dns record with name '" . $CommentString . "'.") false; -} -:local PlaceBefore ([ /ip/dns/static/find where (name=$CommentString or (comment=$CommentString and name=-)) type=NXDOMAIN disabled ]->0); + :local Zone \ + ([ $IfThenElse ($PrefixInZone = true) "ipsec." ] . \ + [ $IfThenElse ($HostNameInZone = true) ($Identity . ".") ] . $Domain); + :local Ttl 5m; + :local CommentPrefix ("managed by " . $ScriptName . " for "); + :local CommentString ("--- " . $ScriptName . " above ---"); -:foreach DnsRecord in=[ /ip/dns/static/find where comment~("^" . $CommentPrefix) ] do={ - :local DnsRecordVal [ /ip/dns/static/get $DnsRecord ]; - :local PeerId [ $CharacterReplace ($DnsRecordVal->"comment") $CommentPrefix "" ]; - :if ([ :len [ /ip/ipsec/active-peers/find where id~("^(CN=)?" . [ $EscapeForRegEx $PeerId ] . "\$") \ - dynamic-address=($DnsRecordVal->"address") ] ] > 0) do={ - $LogPrintExit2 debug $0 ("Peer " . $PeerId . " (" . $DnsRecordVal->"name" . ") still exists. Not deleting DNS entry.") false; - } else={ - :local Found false; - $LogPrintExit2 info $0 ("Peer " . $PeerId . " (" . $DnsRecordVal->"name" . ") has gone, deleting DNS entry.") false; - /ip/dns/static/remove $DnsRecord; + :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 . ".") "." ] ]; + :foreach Peer in=[ /ip/ipsec/active-peers/find where !(dynamic-address=[]) ] do={ + :local PeerVal [ /ip/ipsec/active-peers/get $Peer ]; + :local PeerId [ $CharacterReplace ($PeerVal->"id") "CN=" "" ]; + :local Comment ($CommentPrefix . $PeerId); + :local HostName [ :pick $PeerId 0 [ :find ($PeerId . ".") "." ] ]; - :local Fqdn ($HostName . "." . $Zone); - :local DnsRecord [ /ip/dns/static/find where name=$Fqdn ]; - :if ([ :len $DnsRecord ] > 0) do={ - :local DnsIp [ /ip/dns/static/get $DnsRecord address ]; - :if ($DnsIp = $PeerVal->"dynamic-address") do={ - $LogPrintExit2 debug $0 ("DNS entry for " . $Fqdn . " does not need updating.") false; + :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={ - $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; + $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; } - } 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; } +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/ipv6-update.rsc b/ipv6-update.rsc index 7e1d34f..94bd1bc 100644 --- a/ipv6-update.rsc +++ b/ipv6-update.rsc @@ -1,79 +1,107 @@ #!rsc by RouterOS # RouterOS script: ipv6-update -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # update firewall and dns settings on IPv6 prefix change -# https://git.eworm.de/cgit/routeros-scripts/about/doc/ipv6-update.md +# https://rsc.eworm.de/doc/ipv6-update.md -:local 0 "ipv6-update"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:local PdPrefix $"pd-prefix"; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; + :global LogPrint; + :global ParseKeyValueStore; + :global ScriptLock; -$ScriptLock $0; + :local NaAddress $"na-address"; + :local NaValid $"na-valid"; + :local PdPrefix $"pd-prefix"; + :local PdValid $"pd-valid"; -:if ([ :typeof $PdPrefix ] = "nothing") do={ - $LogPrintExit2 error $0 ("This script is supposed to run from ipv6 dhcp-client.") true; -} + :if ([ $ScriptLock $ScriptName ] = false) do={ + :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); - $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 ([ :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={ - $LogPrintExit2 info $0 ("Updating IPv6 address list with new IPv6 prefix " . $PdPrefix) false; - /ipv6/firewall/address-list/set address=$PdPrefix $AddrList; + :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; + # 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") ]; + :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 ]; + :local Prefix [ /ipv6/address/find where from-pool=$Pool interface=($Comment->"interface") global ]; + :if ([ :len $Prefix ] = 1) do={ + :set Prefix [ /ipv6/address/get $Prefix address ]; - :if ([ :typeof [ :find ($ListEntryVal->"address") "/128" ] ] = "num" ) do={ - :set Prefix ([ :toip6 [ :pick $Prefix 0 [ :find $Prefix "/64" ] ] ] & ffff:ffff:ffff:ffff::); - :local Address ($ListEntryVal->"address"); - :local Address ($Prefix | ([ :toip6 [ :pick $Address 0 [ :find $Address "/128" ] ] ] & ::ffff:ffff:ffff:ffff)); - - $LogPrintExit2 info $0 ("Updating IPv6 address list with new IPv6 host address " . $Address . \ - " from interface " . ($Comment->"interface")) false; - /ipv6/firewall/address-list/set address=$Address $ListEntry; - } else={ - $LogPrintExit2 info $0 ("Updating IPv6 address list with new IPv6 prefix " . $Prefix . \ - " from interface " . ($Comment->"interface")) false; - /ipv6/firewall/address-list/set address=$Prefix $ListEntry; + :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") ]; + :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)); + :local Prefix [ /ipv6/address/find where from-pool=$Pool interface=($Comment->"interface") global ]; + :if ([ :len $Prefix ] = 1) do={ + :set Prefix [ /ipv6/address/get $Prefix address ]; + :set Prefix ([ :toip6 [ :pick $Prefix 0 [ :find $Prefix "/64" ] ] ] & ffff:ffff:ffff:ffff::); + :local Address ($Prefix | ([ :toip6 ($RecordVal->"address") ] & ::ffff:ffff:ffff:ffff)); - $LogPrintExit2 info $0 ("Updating DNS record for " . ($RecordVal->"name") . \ - ($RecordVal->"regexp") . " to " . $Address) false; - /ip/dns/static/set address=$Address $Record; + $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/lease-script.rsc b/lease-script.rsc index f8bc129..bf27fda 100644 --- a/lease-script.rsc +++ b/lease-script.rsc @@ -1,50 +1,65 @@ #!rsc by RouterOS # RouterOS script: lease-script -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # run scripts on DHCP lease -# https://git.eworm.de/cgit/routeros-scripts/about/doc/lease-script.md +# https://rsc.eworm.de/doc/lease-script.md -:local 0 "lease-script"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global Grep; -:global IfThenElse; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; - -:if ([ :typeof $leaseActIP ] = "nothing" || \ - [ :typeof $leaseActMAC ] = "nothing" || \ - [ :typeof $leaseServerName ] = "nothing" || \ - [ :typeof $leaseBound ] = "nothing") do={ - $LogPrintExit2 error $0 ("This script is supposed to run from ip dhcp-server.") true; -} +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$LogPrintExit2 debug $0 ("DHCP Server " . $leaseServerName . " " . [ $IfThenElse ($leaseBound = 0) \ - "de" "" ] . "assigned lease " . $leaseActIP . " to " . $leaseActMAC) false; + :global Grep; + :global IfThenElse; + :global LogPrint; + :global ParseKeyValueStore; + :global ScriptLock; -$ScriptLock $0 false 10; + :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; + } -:if ([ :len [ /system/script/job/find where script=$0 ] ] > 1) do={ - $LogPrintExit2 debug $0 ("More invocations are waiting, exiting early.") true; -} + $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; + } -: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, ") ] ]; + :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; + } - :set ($RunOrder->($Store->"order" . "-" . $ScriptVal->"name")) ($ScriptVal->"name"); -} + :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 { - $LogPrintExit2 debug $0 ("Running script with order " . $Order . ": " . $Script) false; - /system/script/run $Script; - } on-error={ - $LogPrintExit2 warning $0 ("Running script '" . $Script . "' failed!") false; + :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.rsc b/leds-day-mode.rsc index ca2e8d8..7344fde 100644 --- a/leds-day-mode.rsc +++ b/leds-day-mode.rsc @@ -1,9 +1,9 @@ #!rsc by RouterOS # RouterOS script: leds-day-mode -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md # # enable LEDs -# https://git.eworm.de/cgit/routeros-scripts/about/doc/leds-mode.md +# https://rsc.eworm.de/doc/leds-mode.md /system/leds/settings/set all-leds-off=never; diff --git a/leds-night-mode.rsc b/leds-night-mode.rsc index cdd8127..8bd028e 100644 --- a/leds-night-mode.rsc +++ b/leds-night-mode.rsc @@ -1,9 +1,9 @@ #!rsc by RouterOS # RouterOS script: leds-night-mode -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md # # disable LEDs -# https://git.eworm.de/cgit/routeros-scripts/about/doc/leds-mode.md +# https://rsc.eworm.de/doc/leds-mode.md /system/leds/settings/set all-leds-off=immediate; diff --git a/leds-toggle-mode.rsc b/leds-toggle-mode.rsc index da972b7..b55e351 100644 --- a/leds-toggle-mode.rsc +++ b/leds-toggle-mode.rsc @@ -1,13 +1,9 @@ #!rsc by RouterOS # RouterOS script: leds-toggle-mode -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md # # toggle LEDs mode -# https://git.eworm.de/cgit/routeros-scripts/about/doc/leds-mode.md +# https://rsc.eworm.de/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; -} +/system/leds/settings/set all-leds-off=(({ "never"="immediate"; "immediate"="never" })->[ get all-leds-off ]); diff --git a/log-forward.rsc b/log-forward.rsc index 96cb257..afeb3f2 100644 --- a/log-forward.rsc +++ b/log-forward.rsc @@ -1,90 +1,113 @@ #!rsc by RouterOS # RouterOS script: log-forward -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2020-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # forward log messages via notification -# https://git.eworm.de/cgit/routeros-scripts/about/doc/log-forward.md +# https://rsc.eworm.de/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; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:global EitherOr; -:global HexToNum; -:global IfThenElse; -:global LogForwardFilterLogForwarding; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; + :global Identity; + :global LogForwardFilter; + :global LogForwardFilterMessage; + :global LogForwardInclude; + :global LogForwardIncludeMessage; + :global LogForwardLast; + :global LogForwardRateLimit; -$ScriptLock $0; + :global EitherOr; + :global HexToNum; + :global IfThenElse; + :global LogForwardFilterLogForwarding; + :global LogPrint; + :global MAX; + :global ScriptLock; + :global SendNotification2; + :global SymbolForNotification; -:if ([ :typeof $LogForwardRateLimit ] = "nothing") do={ - :set LogForwardRateLimit 0; -} + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } -:if ($LogForwardRateLimit > 30) do={ - :set LogForwardRateLimit ($LogForwardRateLimit - 1); - $LogPrintExit2 info $0 ("Rate limit in action, not forwarding logs, if any!") true; -} + :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 ({}); + :local Count 0; + :local Duplicates false; + :local Last [ $IfThenElse ([ :len $LogForwardLast ] > 0) [ $HexToNum $LogForwardLast ] -1 ]; + :local Messages ""; + :local Warning false; + :local MessageVal; + :local MessageDups ({}); -:local LogForwardFilterLogForwardingCached [ $EitherOr [ $LogForwardFilterLogForwarding ] ("\$^") ]; -:foreach Message in=[ /log/find where (!(message="") and \ - !(message~$LogForwardFilterLogForwardingCached) and \ - !(topics~$LogForwardFilter) and !(message~$LogForwardFilterMessage)) or \ - topics~$LogForwardInclude or message~$LogForwardIncludeMessage ] do={ - :set MessageVal [ /log/get $Message ]; + :set LogForwardFilter [ $EitherOr $LogForwardFilter [] ]; + :set LogForwardFilterMessage [ $EitherOr $LogForwardFilterMessage [] ]; + :set LogForwardInclude [ $EitherOr $LogForwardInclude [] ]; + :set LogForwardIncludeMessage [ $EitherOr $LogForwardIncludeMessage [] ]; - :if ($Last < [ $HexToNum ($MessageVal->".id") ]) do={ - :local DupCount ($MessageDups->($MessageVal->"message")); - :if ($MessageVal->"topics" ~ "(emergency|alert|critical|error|warning)") do={ - :set Warning true; - } - :if ($DupCount < 3) do={ - :set Messages ($Messages . "\n" . [ $IfThenElse ($NotificationsWithSymbols = true) (" \E2\97\8F ") ] . \ - $MessageVal->"time" . " " . [ :tostr ($MessageVal->"topics") ] . " " . $MessageVal->"message"); - } else={ - :set Duplicates true; + :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); } - :set ($MessageDups->($MessageVal->"message")) ($DupCount + 1); - :set Count ($Count + 1); } -} -:if ($Count > 0) do={ - :set LogForwardRateLimit ($LogForwardRateLimit + 10); + :if ($Count > 0) do={ + :set LogForwardRateLimit ($LogForwardRateLimit + 10); - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification [ $IfThenElse ($Warning = true) "warning-sign" "memo" ] ] . \ - "Log Forwarding"); \ - message=("The log on " . $Identity . " contains " . [ $IfThenElse ($Count = 1) "this message" \ - ("these " . $Count . " messages") ] . " after " . [ /system/resource/get uptime ] . " uptime." . \ - [ $IfThenElse ($Duplicates = true) (" Multi-repeated messages have been skipped.") ] . \ - [ $IfThenElse ($LogForwardRateLimit > 30) ("\nRate limit in action, delaying forwarding.") ] . \ - "\n" . $Messages) }); - - :set LogForwardLast ($MessageVal->".id"); -} else={ - :if ($LogForwardRateLimit > 0) do={ - :set LogForwardRateLimit ($LogForwardRateLimit - 1); + $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 ]; } Binary files differBinary files differdiff --git a/mod/bridge-port-to.rsc b/mod/bridge-port-to.rsc index 86689c9..39a036e 100644 --- a/mod/bridge-port-to.rsc +++ b/mod/bridge-port-to.rsc @@ -1,18 +1,20 @@ #!rsc by RouterOS # RouterOS script: mod/bridge-port-to -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # reset bridge ports to default bridge -# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/bridge-port-to.md +# https://rsc.eworm.de/doc/mod/bridge-port-to.md :global BridgePortTo; -:set BridgePortTo do={ +:set BridgePortTo do={ :do { :local BridgePortTo [ :tostr $1 ]; :global IfThenElse; - :global LogPrintExit2; + :global LogPrint; :global ParseKeyValueStore; :local InterfaceReEnable ({}); @@ -24,21 +26,22 @@ :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; + $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={ - $LogPrintExit2 info $0 ("Disabling bridge port for interface " . $BridgePortVal->"interface" . ", enabling dhcp client.") false; + $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={ - $LogPrintExit2 info $0 ("Enabling bridge port for interface " . $BridgePortVal->"interface" . ", changing to " . $BridgePortTo . \ - " bridge " . $BridgeDefault . ", disabling dhcp client.") false; + $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; @@ -50,8 +53,8 @@ } /interface/bridge/port/set disabled=no bridge=$BridgeDefault $BridgePort; } else={ - $LogPrintExit2 debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $BridgePortTo . \ - " bridge " . $BridgeDefault . ".") false; + $LogPrint debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $BridgePortTo . \ + " bridge " . $BridgeDefault . "."); } } } @@ -59,7 +62,9 @@ } :if ([ :len $InterfaceReEnable ] > 0) do={ :delay 5s; - $LogPrintExit2 info $0 ("Re-enabling interfaces...") false; + $LogPrint info $0 ("Re-enabling interfaces..."); /interface/ethernet/enable $InterfaceReEnable; } -} +} on-error={ + :global ExitError; $ExitError false $0; +} } diff --git a/mod/bridge-port-vlan.rsc b/mod/bridge-port-vlan.rsc index 792a6c4..0eeb9b5 100644 --- a/mod/bridge-port-vlan.rsc +++ b/mod/bridge-port-vlan.rsc @@ -1,18 +1,20 @@ #!rsc by RouterOS # RouterOS script: mod/bridge-port-vlan -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # manage VLANs on bridge ports -# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/bridge-port-vlan.md +# https://rsc.eworm.de/doc/mod/bridge-port-vlan.md :global BridgePortVlan; -:global BridgePortVlan do={ +:global BridgePortVlan do={ :do { :local ConfigTo [ :tostr $1 ]; :global IfThenElse; - :global LogPrintExit2; + :global LogPrint; :global ParseKeyValueStore; :local InterfaceReEnable ({}); @@ -24,13 +26,14 @@ :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; + $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={ - $LogPrintExit2 info $0 ("Disabling bridge port for interface " . $BridgePortVal->"interface" . ", enabling dhcp client.") false; + $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; @@ -41,12 +44,13 @@ :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; + $LogPrint warning $0 ("Could not find VLAN '" . $Vlan . "' for interface " . $BridgePortVal->"interface" . "!"); + :return false; } } :if ($BridgePortVal->"disabled" = true || $Vlan != $BridgePortVal->"pvid") do={ - $LogPrintExit2 info $0 ("Enabling bridge port for interface " . $BridgePortVal->"interface" . ", changing to " . $ConfigTo . \ - " vlan " . $Vlan . [ $IfThenElse ($Vlan != $VlanName) (" (" . $VlanName . ")") ] . ", disabling dhcp client.") false; + $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; @@ -58,8 +62,8 @@ } /interface/bridge/port/set disabled=no pvid=$Vlan $BridgePort; } else={ - $LogPrintExit2 debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $ConfigTo . \ - " vlan " . $Vlan . ".") false; + $LogPrint debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $ConfigTo . \ + " vlan " . $Vlan . "."); } } } @@ -67,7 +71,9 @@ } :if ([ :len $InterfaceReEnable ] > 0) do={ :delay 5s; - $LogPrintExit2 info $0 ("Re-enabling interfaces...") false; + $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.rsc index e06d799..c861557 100644 --- a/mod/inspectvar.rsc +++ b/mod/inspectvar.rsc @@ -1,21 +1,24 @@ #!rsc by RouterOS # RouterOS script: mod/inspectvar -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2020-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # inspect variables -# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/inspectvar.md +# https://rsc.eworm.de/doc/mod/inspectvar.md :global InspectVar; :global InspectVarReturn; # inspect variable and print on terminal -:set InspectVar do={ +:set InspectVar do={ :do { :global InspectVarReturn; - :global PrettyPrint; - $PrettyPrint [ $InspectVarReturn $1 ]; -} + :put [ :tocrlf [ $InspectVarReturn $1 ] ]; +} on-error={ + :global ExitError; $ExitError false $0; +} } # inspect variable and return formatted string :set InspectVarReturn do={ @@ -39,7 +42,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.rsc index eaa5c97..477cf4a 100644 --- a/mod/ipcalc.rsc +++ b/mod/ipcalc.rsc @@ -1,32 +1,35 @@ #!rsc by RouterOS # RouterOS script: mod/ipcalc -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2020-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # ip address calculation -# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/ipcalc.md +# https://rsc.eworm.de/doc/mod/ipcalc.md :global IPCalc; :global IPCalcReturn; # print netmask, network, min host, max host and broadcast -:set IPCalc do={ +:set IPCalc do={ :do { :local Input [ :tostr $1 ]; :global FormatLine; :global IPCalcReturn; - :global PrettyPrint; :local Values [ $IPCalcReturn $1 ]; - $PrettyPrint ( \ + :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") ]); -} + [ $FormatLine "Broadcast" ($Values->"broadcast") ]) ]; +} on-error={ + :global ExitError; $ExitError false $0; +} } # calculate and return netmask, network, min host, max host and broadcast :set IPCalcReturn do={ diff --git a/mod/notification-email.rsc b/mod/notification-email.rsc index e266201..7b89d98 100644 --- a/mod/notification-email.rsc +++ b/mod/notification-email.rsc @@ -1,11 +1,15 @@ #!rsc by RouterOS # RouterOS script: mod/notification-email -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 +# requires device-mode, email, scheduler # # send notifications via e-mail -# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/notification-email.md +# https://rsc.eworm.de/doc/mod/notification-email.md +:global EMailGenerateFrom; :global FlushEmailQueue; :global LogForwardFilterLogForwarding; :global NotificationEMailSubject; @@ -15,43 +19,73 @@ :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={ +:set FlushEmailQueue do={ :do { :global EmailQueue; :global EitherOr; + :global EMailGenerateFrom; :global IsDNSResolving; :global IsTimeSync; - :global LogPrintExit2; + :global LogPrint; :local AllDone true; :local QueueLen [ :len $EmailQueue ]; - :local Scheduler [ /system/scheduler/find where name=$0 ]; - - :if ([ :len $Scheduler ] > 0 && [ /system/scheduler/get $Scheduler interval ] < 1m) do={ + :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={ - $LogPrintExit2 debug $0 ("Sending mail is currently in progress, not flushing.") false; + $LogPrint debug $0 ("Sending mail is currently in progress, not flushing."); :return false; } :if ([ $IsTimeSync ] = false) do={ - $LogPrintExit2 debug $0 ("Time is not synced, not flushing.") false; + $LogPrint debug $0 ("Time is not synced, not flushing."); :return false; } - :if ([ :typeof [ :toip [ /tool/e-mail/get address ] ] ] != "ip" && [ $IsDNSResolving ] = false) do={ - $LogPrintExit2 debug $0 ("Server address is a DNS name and resolving fails, not flushing.") false; + :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; } - :if ([ :len $Scheduler ] > 0 && $QueueLen = 0) do={ - $LogPrintExit2 warning $0 ("Flushing E-Mail messages from scheduler, but queue is empty.") false; - } - - /system/scheduler/set interval=([ $EitherOr $QueueLen 1 ] . "m") comment="Sending..." $Scheduler; + /system/scheduler/set interval=($QueueLen . "m") comment="Sending..." $Scheduler; :foreach Id,Message in=$EmailQueue do={ :if ([ :typeof $Message ] = "array" ) do={ @@ -61,11 +95,11 @@ :if ([ :len [ /file/find where name=$File ] ] = 1) do={ :set Attach ($Attach, $File); } else={ - $LogPrintExit2 warning $0 ("File '" . $File . "' does not exist, can not attach.") false; + $LogPrint warning $0 ("File '" . $File . "' does not exist, can not attach."); } } - /tool/e-mail/send to=($Message->"to") cc=($Message->"cc") subject=($Message->"subject") \ - body=($Message->"body") file=$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; @@ -90,10 +124,20 @@ :if ($AllDone = true && $QueueLen = [ :len $EmailQueue ]) do={ /system/scheduler/remove $Scheduler; :set EmailQueue; - } else={ - /system/scheduler/set interval=1m comment="Waiting for retry..." $Scheduler; + :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={ @@ -130,20 +174,21 @@ :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->"address") = "0.0.0.0" || ($EMailSettings->"from") = "<>") do={ + :if ([ :len $To ] = 0 || ($EMailSettings->"server") = "0.0.0.0" || ($EMailSettings->"from") = "<>") do={ :return false; } :if ([ :typeof $EmailQueue ] = "nothing") do={ :set EmailQueue ({}); } - :local Signature [ /system/note/get note ]; + :local Signature [ $EitherOr [ $NotificationEMailSignature ] [ /system/note/get note ] ]; :set ($EmailQueue->[ :len $EmailQueue ]) { to=$To; cc=$Cc; subject=[ $NotificationEMailSubject ($Notification->"subject") ]; @@ -151,8 +196,8 @@ [ $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 \ + :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;"); } } @@ -161,7 +206,7 @@ :set PurgeEMailQueue do={ :global EmailQueue; - /system/scheduler/remove [ find where name="\$FlushEmailQueue" ]; + /system/scheduler/remove [ find where name="_FlushEmailQueue" ]; :set EmailQueue; } @@ -169,27 +214,28 @@ :global QuotedPrintable do={ :local Input [ :tostr $1 ]; + :global CharacterMultiply; + :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" }; + :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 ($Char = "=") do={ - :set Char "=3D"; - } :if ([ :typeof $Replace ] = "num") do={ - :set Char ("=" . ($Hex->($Replace / 16 + 8)) . ($Hex->($Replace % 16))); + :set Char ("=" . [ :pick $Hex ($Replace / 16)] . [ :pick $Hex ($Replace % 16) ]); } :set Return ($Return . $Char); } @@ -202,11 +248,13 @@ } # send notification via e-mail - expects at least two string arguments -:set SendEMail do={ +:set SendEMail do={ :do { :global SendEMail2; - $SendEMail2 ({ subject=$1; message=$2; link=$3 }); -} + $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={ diff --git a/mod/notification-gotify.rsc b/mod/notification-gotify.rsc new file mode 100644 index 0000000..7070918 --- /dev/null +++ b/mod/notification-gotify.rsc @@ -0,0 +1,139 @@ +#!rsc by RouterOS +# RouterOS script: mod/notification-gotify +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# Leonardo David Monteiro <leo@cub3.xyz> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 +# requires device-mode, fetch, scheduler +# +# send notifications via Gotify (gotify.net) +# https://rsc.eworm.de/doc/mod/notification-gotify.md + +:global FlushGotifyQueue; +:global NotificationFunctions; +:global PurgeGotifyQueue; +:global SendGotify; +:global SendGotify2; + +# flush Gotify queue +:set FlushGotifyQueue do={ :do { + :global GotifyQueue; + + :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 $GotifyQueue ]; + + :if ([ :len [ /system/scheduler/find where name="_FlushGotifyQueue" ] ] > 0 && $QueueLen = 0) do={ + $LogPrint warning $0 ("Flushing Gotify messages from scheduler, but queue is empty."); + } + + :foreach Id,Message in=$GotifyQueue 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=[ :serialize to=json ($Message->"message") ] \ + ($Message->"url") as-value; + :set ($GotifyQueue->$Id); + } on-error={ + $LogPrint debug $0 ("Sending queued Gotify message failed."); + :set AllDone false; + } + } + } + + :if ($AllDone = true && $QueueLen = [ :len $GotifyQueue ]) do={ + /system/scheduler/remove [ find where name="_FlushGotifyQueue" ]; + :set GotifyQueue; + } +} on-error={ + :global ExitError; $ExitError false $0; +} } + +# send notification via Gotify - expects one array argument +:set ($NotificationFunctions->"gotify") do={ + :local Notification $1; + + :global Identity; + :global IdentityExtra; + :global GotifyQueue; + :global GotifyServer; + :global GotifyServerOverride; + :global GotifyToken; + :global GotifyTokenOverride; + + :global EitherOr; + :global FetchUserAgentStr; + :global IfThenElse; + :global LogPrint; + :global SymbolForNotification; + + :local Server [ $EitherOr ($GotifyServerOverride->($Notification->"origin")) $GotifyServer ]; + :local Token [ $EitherOr ($GotifyTokenOverride->($Notification->"origin")) $GotifyToken ]; + + :if ([ :len $Token ] = 0) do={ + :return false; + } + + :local Url ("https://" . $Server . "/message"); + :local Headers ({ [ $FetchUserAgentStr ($Notification->"origin") ]; \ + ("X-Gotify-Key: " . $Token); "Content-Type: application/json" }); + :local Message ({ + "title"=("[" . $IdentityExtra . $Identity . "] " . ($Notification->"subject")); \ + "message"=(($Notification->"message") . "\n" . [ $IfThenElse ([ :len ($Notification->"link") ] > 0) \ + ("\n" . [ $SymbolForNotification "link" ] . ($Notification->"link")) ]); \ + "priority"=[ :tonum [ $IfThenElse ($Notification->"silent") 2 5 ] ] }); + + :do { + /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ + http-header-field=$Headers http-data=[ :serialize to=json $Message ] $Url as-value; + } on-error={ + $LogPrint info $0 ("Failed sending Gotify notification! Queuing..."); + + :if ([ :typeof $GotifyQueue ] = "nothing") do={ + :set GotifyQueue ({}); + } + :set ($Message->"message") (($Notification->"message") . "\n" . \ + [ $SymbolForNotification "alarm-clock" ] . "This message was queued since " . \ + [ /system/clock/get date ] . " " . [ /system/clock/get time ] . " and may be obsolete."); + :set ($GotifyQueue->[ :len $GotifyQueue ]) \ + { url=$Url; headers=$Headers; message=$Message }; + :if ([ :len [ /system/scheduler/find where name="_FlushGotifyQueue" ] ] = 0) do={ + /system/scheduler/add name="_FlushGotifyQueue" interval=1m start-time=startup \ + on-event=(":global FlushGotifyQueue; \$FlushGotifyQueue;"); + } + } +} + +# purge the Gotify queue +:set PurgeGotifyQueue do={ + :global GotifyQueue; + + /system/scheduler/remove [ find where name="_FlushGotifyQueue" ]; + :set GotifyQueue; +} + +# send notification via Gotify - expects at least two string arguments +:set SendGotify do={ :do { + :global SendGotify2; + + $SendGotify2 ({ origin=$0; subject=$1; message=$2; link=$3; silent=$4 }); +} on-error={ + :global ExitError; $ExitError false $0; +} } + +# send notification via Gotify - expects one array argument +:set SendGotify2 do={ + :local Notification $1; + + :global NotificationFunctions; + + ($NotificationFunctions->"gotify") ("\$NotificationFunctions->\"gotify\"") $Notification; +} diff --git a/mod/notification-matrix.rsc b/mod/notification-matrix.rsc index 03cfb13..e989ee0 100644 --- a/mod/notification-matrix.rsc +++ b/mod/notification-matrix.rsc @@ -1,59 +1,66 @@ #!rsc by RouterOS # RouterOS script: mod/notification-matrix -# Copyright (c) 2013-2023 Michael Gisbers <michael@gisbers.de> +# Copyright (c) 2013-2025 Michael Gisbers <michael@gisbers.de> # Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 +# requires device-mode, fetch, scheduler # # send notifications via Matrix -# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/notification-matrix.md +# 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={ +:set FlushMatrixQueue do={ :do { :global MatrixQueue; :global IsFullyConnected; - :global LogPrintExit2; + :global LogPrint; :if ([ $IsFullyConnected ] = false) do={ - $LogPrintExit2 debug $0 ("System is not fully connected, not flushing.") false; + $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=$0 ] ] > 0 && $QueueLen = 0) do={ - $LogPrintExit2 warning $0 ("Flushing Matrix messages from scheduler, but queue is empty.") false; + :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-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; + /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={ - $LogPrintExit2 debug $0 ("Sending queued Matrix message failed.") false; + $LogPrint debug $0 ("Sending queued Matrix message failed."); :set AllDone false; } } } :if ($AllDone = true && $QueueLen = [ :len $MatrixQueue ]) do={ - /system/scheduler/remove [ find where name=$0 ]; + /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={ @@ -70,7 +77,9 @@ :global MatrixRoomOverride; :global EitherOr; - :global LogPrintExit2; + :global FetchUserAgentStr; + :global LogPrint; + :global ProtocolStrip; :global SymbolForNotification; :local PrepareText do={ @@ -81,21 +90,15 @@ } :local Return ""; - :local Chars { - "plain"={ "\\"; "\""; "\n" }; - "format"={ "\\"; "\""; "\n"; "&"; "<"; ">" }; - } - :local Subs { - "plain"={ "\\\\"; "\\\""; "\\n" }; - "format"={ "\\\\"; """; "<br/>"; "&"; "<"; ">" }; - } + :local Chars { "\""; "\n"; "&"; "<"; ">" }; + :local Subs { """; "<br/>"; "&"; "<"; ">" }; :for I from=0 to=([ :len $Input ] - 1) do={ :local Char [ :pick $Input $I ]; - :local Replace [ :find ($Chars->$2) $Char ]; + :local Replace [ :find $Chars $Char ]; :if ([ :typeof $Replace ] = "num") do={ - :set Char ($Subs->$2->$Replace); + :set Char ($Subs->$Replace); } :set Return ($Return . $Char); } @@ -111,42 +114,45 @@ :return false; } - :local Plain [ $PrepareText ("## [" . $IdentityExtra . $Identity . "] " . \ - ($Notification->"subject") . "\n```\n" . ($Notification->"message") . "\n```") "plain" ]; + :local Headers ({ [ $FetchUserAgentStr ($Notification->"origin") ] }); + :local Plain ("## [" . $IdentityExtra . $Identity . "] " . \ + ($Notification->"subject") . "\n```\n" . ($Notification->"message") . "\n```"); :local Formatted ("<h2>" . [ $PrepareText ("[" . $IdentityExtra . $Identity . "] " . \ - ($Notification->"subject")) "format" ] . "</h2>" . "<pre><code>" . \ - [ $PrepareText ($Notification->"message") "format" ] . "</code></pre>"); + ($Notification->"subject")) ] . "</h2>" . "<pre><code>" . \ + [ $PrepareText ($Notification->"message") ] . "</code></pre>"); :if ([ :len ($Notification->"link") ] > 0) do={ - :set Plain ($Plain . "\\n" . [ $SymbolForNotification "link" ] . \ - [ $PrepareText ("[" . $Notification->"link" . "](" . $Notification->"link" . ")") "plain" ]); + :local Label [ $ProtocolStrip ($Notification->"link") ]; + :set Plain ($Plain . "\n" . [ $SymbolForNotification "link" ] . \ + "[" . $Label . "](" . $Notification->"link" . ")"); :set Formatted ($Formatted . "<br/>" . [ $SymbolForNotification "link" ] . \ - "<a href=\\\"" . [ $PrepareText ($Notification->"link") "format" ] . "\\\">" . \ - [ $PrepareText ($Notification->"link") "format" ] . "</a>"); + "<a href=\"" . [ $PrepareText ($Notification->"link") ] . "\">" . \ + [ $PrepareText $Label ] . "</a>"); } :do { - /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ - ("https://" . $HomeServer . "/_matrix/client/r0/rooms/" . $Room . \ - "/send/m.room.message?access_token=" . $AccessToken) \ - http-data=("{ \"msgtype\": \"m.text\", \"body\": \"" . $Plain . "\"," . \ - "\"format\": \"org.matrix.custom.html\", \"formatted_body\": \"" . \ - $Formatted . "\" }") as-value; + /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={ - $LogPrintExit2 info $0 ("Failed sending Matrix notification! Queuing...") false; + $LogPrint info $0 ("Failed sending Matrix notification! Queuing..."); :if ([ :typeof $MatrixQueue ] = "nothing") do={ :set MatrixQueue ({}); } - :local Text ([ $SymbolForNotification "alarm-clock" ] . \ - "This message was queued since " . [ /system/clock/get date ] . \ - " " . [ /system/clock/get time ] . " and may be obsolete."); - :set Plain ($Plain . "\\n" . $Text); - :set Formatted ($Formatted . "<br/>" . $Text); - :set ($MatrixQueue->[ :len $MatrixQueue ]) { room=$Room; \ - accesstoken=$AccessToken; homeserver=$HomeServer; \ - plain=$Plain; formatted=$Formatted }; - :if ([ :len [ /system/scheduler/find where name="\$FlushMatrixQueue" ] ] = 0) do={ - /system/scheduler/add name="\$FlushMatrixQueue" interval=1m start-time=startup \ + :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 . "<br/>" . $Symbol . "This message was queued since <em>" . \ + $DateTime . "</em> 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;"); } } @@ -156,16 +162,18 @@ :set PurgeMatrixQueue do={ :global MatrixQueue; - /system/scheduler/remove [ find where name="\$FlushMatrixQueue" ]; + /system/scheduler/remove [ find where name="_FlushMatrixQueue" ]; :set MatrixQueue; } # send notification via Matrix - expects at least two string arguments -:set SendMatrix do={ +:set SendMatrix do={ :do { :global SendMatrix2; - $SendMatrix2 ({ subject=$1; message=$2; link=$3 }); -} + $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={ @@ -175,3 +183,89 @@ ($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 new file mode 100644 index 0000000..2748c5b --- /dev/null +++ b/mod/notification-ntfy.rsc @@ -0,0 +1,161 @@ +#!rsc by RouterOS +# RouterOS script: mod/notification-ntfy +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# 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 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.rsc b/mod/notification-telegram.rsc index ea47b1a..5ef353b 100644 --- a/mod/notification-telegram.rsc +++ b/mod/notification-telegram.rsc @@ -1,59 +1,102 @@ #!rsc by RouterOS # RouterOS script: mod/notification-telegram -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 +# requires device-mode, fetch, scheduler # # send notifications via Telegram -# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/notification-telegram.md +# 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={ +:set FlushTelegramQueue do={ :do { :global TelegramQueue; + :global TelegramMessageIDs; :global IsFullyConnected; - :global LogPrintExit2; + :global LogPrint; :if ([ $IsFullyConnected ] = false) do={ - $LogPrintExit2 debug $0 ("System is not fully connected, not flushing.") false; + $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=$0 ] ] > 0 && $QueueLen = 0) do={ - $LogPrintExit2 warning $0 ("Flushing Telegram messages from scheduler, but queue is empty.") false; + :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 { - /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ + :local Data ([ /tool/fetch check-certificate=yes-without-crl output=user http-method=post \ ("https://api.telegram.org/bot" . ($Message->"tokenid") . "/sendMessage") \ - http-data=("chat_id=" . ($Message->"chatid") . \ - "&disable_notification=" . ($Message->"silent") . \ - "&reply_to_message_id=" . ($Message->"replyto") . \ - "&disable_web_page_preview=true&parse_mode=" . ($Message->"parsemode") . \ - "&text=" . ($Message->"text")) as-value; + 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={ - $LogPrintExit2 debug $0 ("Sending queued Telegram message failed.") false; + $LogPrint debug $0 ("Sending queued Telegram message failed."); :set AllDone false; } } } :if ($AllDone = true && $QueueLen = [ :len $TelegramQueue ]) do={ - /system/scheduler/remove [ find where name=$0 ]; + /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={ @@ -63,8 +106,10 @@ :global IdentityExtra; :global TelegramChatId; :global TelegramChatIdOverride; - :global TelegramFixedWidthFont; + :global TelegramMessageIDs; :global TelegramQueue; + :global TelegramThreadId; + :global TelegramThreadIdOverride; :global TelegramTokenId; :global TelegramTokenIdOverride; @@ -72,51 +117,58 @@ :global CharacterReplace; :global EitherOr; :global IfThenElse; - :global LogPrintExit2; + :global LogPrint; + :global ProtocolStrip; :global SymbolForNotification; :global UrlEncode; :local EscapeMD do={ - :global TelegramFixedWidthFont; + :local Text [ :tostr $1 ]; + :local Mode [ :tostr $2 ]; + :local Excl [ :tostr $3 ]; :global CharacterReplace; :global IfThenElse; - :if ($TelegramFixedWidthFont != true) do={ - :return ($1 . [ $IfThenElse ($2 = "body") ("\n") "" ]); - } - - :local Return $1; :local Chars { - "body"={ "\\"; "`" }; + "body"={ "\\"; "`" }; "plain"={ "_"; "*"; "["; "]"; "("; ")"; "~"; "`"; ">"; "#"; "+"; "-"; "="; "|"; "{"; "}"; "."; "!" }; } - :foreach Char in=($Chars->$2) do={ - :set Return [ $CharacterReplace $Return $Char ("\\" . $Char) ]; + :foreach Char in=($Chars->$Mode) do={ + :if ([ :typeof [ :find $Excl $Char ] ] = "nil") do={ + :set Text [ $CharacterReplace $Text $Char ("\\" . $Char) ]; + } } - :if ($2 = "body") do={ - :return ("```\n" . $Return . "\n```"); + :if ($Mode = "body") do={ + :return ("```\n" . $Text . "\n```"); } - :return $Return; + :return $Text; } :local ChatId [ $EitherOr ($Notification->"chatid") \ [ $EitherOr ($TelegramChatIdOverride->($Notification->"origin")) $TelegramChatId ] ]; + :local ThreadId [ $EitherOr ($Notification->"threadid") \ + [ $EitherOr ($TelegramThreadIdOverride->($Notification->"origin")) \ + [ $IfThenElse ([ :len ($TelegramChatIdOverride->($Notification->"origin")) ] = 0) $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") ]; + :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" ]); @@ -125,39 +177,41 @@ :set Text ($Text . [ $EscapeMD ($Notification->"message") "body" ]); } :if ($LenLink > 0) do={ - :set Text ($Text . "\n" . [ $SymbolForNotification "link" ] . [ $EscapeMD ($Notification->"link") "plain" ]); + :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" ]); + [ $EscapeMD ("The message was too long and has been truncated, cut off _" . \ + (($LenSum - [ :len $Text ]) * 100 / $LenSum) . "%_!") "plain" "_" ]); } - :set Text [ $UrlEncode $Text ]; - :local ParseMode [ $IfThenElse ($TelegramFixedWidthFont = true) "MarkdownV2" "" ]; + :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 Secure Certificate Authority - G2" ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true; + :if ([ $CertificateAvailable "Go Daddy Root Certificate Authority - G2" ] = false) do={ + $LogPrint warning $0 ("Downloading required certificate failed."); + :error false; } - /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ + :local Data ([ /tool/fetch check-certificate=yes-without-crl output=user http-method=post \ ("https://api.telegram.org/bot" . $TokenId . "/sendMessage") \ - http-data=("chat_id=" . $ChatId . "&disable_notification=" . ($Notification->"silent") . \ - "&reply_to_message_id=" . ($Notification->"replyto") . \ - "&disable_web_page_preview=true&parse_mode=" . $ParseMode . "&text=" . $Text) as-value; + http-data=($HTTPData . "&text=" . [ $UrlEncode $Text ]) as-value ]->"data"); + :set ($TelegramMessageIDs->[ :tostr ([ :deserialize from=json value=$Data ]->"result"->"message_id") ]) 1; } on-error={ - $LogPrintExit2 info $0 ("Failed sending telegram notification! Queuing...") false; + $LogPrint info $0 ("Failed sending Telegram notification! Queuing..."); :if ([ :typeof $TelegramQueue ] = "nothing") do={ :set TelegramQueue ({}); } - :set Text ($Text . [ $UrlEncode ("\n" . [ $SymbolForNotification "alarm-clock" ] . \ - [ $EscapeMD ("This message was queued since " . [ /system/clock/get date ] . \ - " " . [ /system/clock/get time ] . " and may be obsolete.") "plain" ]) ]); - :set ($TelegramQueue->[ :len $TelegramQueue ]) { chatid=$ChatId; tokenid=$TokenId; - parsemode=$ParseMode; text=$Text; silent=($Notification->"silent"); - replyto=($Notification->"replyto") }; - :if ([ :len [ /system/scheduler/find where name="\$FlushTelegramQueue" ] ] = 0) do={ - /system/scheduler/add name="\$FlushTelegramQueue" interval=1m start-time=startup \ + :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;"); } } @@ -167,16 +221,18 @@ :set PurgeTelegramQueue do={ :global TelegramQueue; - /system/scheduler/remove [ find where name="\$FlushTelegramQueue" ]; + /system/scheduler/remove [ find where name="_FlushTelegramQueue" ]; :set TelegramQueue; } # send notification via telegram - expects at least two string arguments -:set SendTelegram do={ +:set SendTelegram do={ :do { :global SendTelegram2; - $SendTelegram2 ({ subject=$1; message=$2; link=$3; silent=$4 }); -} + $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={ diff --git a/mod/scriptrunonce.rsc b/mod/scriptrunonce.rsc index 199d852..7fcd5b5 100644 --- a/mod/scriptrunonce.rsc +++ b/mod/scriptrunonce.rsc @@ -1,49 +1,56 @@ #!rsc by RouterOS # RouterOS script: mod/scriptrunonece -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2020-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # download script and run it once -# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/scriptrunonce.md +# https://rsc.eworm.de/doc/mod/scriptrunonce.md :global ScriptRunOnce; # fetch and run script(s) once -:set ScriptRunOnce do={ +:set ScriptRunOnce do={ :do { :local Scripts [ :toarray $1 ]; :global ScriptRunOnceBaseUrl; :global ScriptRunOnceUrlSuffix; - :global LogPrintExit2; + :global FetchHuge; + :global LogPrint; :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; + $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; - :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; + :local Source [ $FetchHuge $0 $Script true ]; + :if ($Source = false) do={ + $LogPrint warning $0 ("Failed fetching script '" . $Script . "'!"); + :return 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; - } + :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 index 31bb3e6..2fae4b1 100644 --- a/mod/ssh-keys-import.rsc +++ b/mod/ssh-keys-import.rsc @@ -1,89 +1,114 @@ #!rsc by RouterOS # RouterOS script: mod/ssh-keys-import -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2020-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.16 # # import ssh keys for public key authentication -# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/ssh-keys-import.md +# https://rsc.eworm.de/doc/mod/ssh-keys-import.md :global SSHKeysImport; :global SSHKeysImportFile; # import single key passed as string -:set SSHKeysImport do={ +:set SSHKeysImport do={ :do { :local Key [ :tostr $1 ]; :local User [ :tostr $2 ]; :global GetRandom20CharAlNum; - :global LogPrintExit2; + :global LogPrint; :global MkDir; - :global RequiredRouterOS; + :global RmDir; :global WaitForFile; :if ([ :len $Key ] = 0 || [ :len $User ] = 0) do={ - $LogPrintExit2 warning $0 ("Missing argument(s), please pass key and user!") true; + $LogPrint warning $0 ("Missing argument(s), please pass key and user!"); + :return false; } :if ([ :len [ /user/find where name=$User ] ] = 0) do={ - $LogPrintExit2 warning $0 ("User '" . $User . "' does not exist.") true; + $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 Type [ :pick $Key 0 [ :find $Key " " ] ]; - :if (!(([ $RequiredRouterOS $0 "7.12beta1" ] = true && $Type = "ssh-ed25519") || $Type = "ssh-rsa")) do={ - $LogPrintExit2 warning $0 ("SSH key of type '" . $Type . "' is not supported.") true; + :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={ - $LogPrintExit2 warning $0 ("Creating directory 'tmpfs/ssh-keys-import' failed!") true; + $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; + /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={ - $LogPrintExit2 warning $0 ("Failed importing key.") true; + $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={ +:set SSHKeysImportFile do={ :do { :local FileName [ :tostr $1 ]; :local User [ :tostr $2 ]; :global EitherOr; - :global LogPrintExit2; + :global LogPrint; :global ParseKeyValueStore; - :global RequiredRouterOS; :global SSHKeysImport; :if ([ :len $FileName ] = 0 || [ :len $User ] = 0) do={ - $LogPrintExit2 warning $0 ("Missing argument(s), please pass file name and user!") true; + $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={ - $LogPrintExit2 warning $0 ("File '" . $FileName . "' does not exist.") true; + $LogPrint warning $0 ("File '" . $FileName . "' does not exist."); + :return false; } - :local Keys ([ /file/get $FileName contents ] . "\n"); + :local Keys [ :tolf [ /file/get $FileName contents ] ]; - :do { + :foreach KeyVal in=[ :deserialize $Keys delimiter=" " from=dsv options=dsv.plain ] do={ :local Continue false; - :local Line [ :pick $Keys 0 [ :find $Keys "\n" ] ]; - :set Keys [ :pick $Keys ([ :find $Keys "\n" ] + 1) [ :len $Keys ] ]; - :local Type [ :pick $Line 0 [ :find $Line " " ] ]; - :if (([ $RequiredRouterOS $0 "7.12beta1" ] = true && $Type = "ssh-ed25519") || $Type = "ssh-rsa") do={ - $SSHKeysImport $Line $User; + :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 && $Type = "#") do={ - :set User [ $EitherOr ([ $ParseKeyValueStore [ :pick $Line 2 [ :len $Line ] ] ]->"user") $User ]; + :if ($Continue = false && $KeyVal->0 = "#") do={ + :set User [ $EitherOr ([ $ParseKeyValueStore ($KeyVal->1) ]->"user") $User ]; :set Continue true; } - :if ($Continue = false && [ :len $Type ] > 0) do={ - $LogPrintExit2 warning $0 ("SSH key of type '" . $Type . "' is not supported.") false; + :if ($Continue = false && [ :len ($KeyVal->0) ] > 0) do={ + $LogPrint warning $0 ("SSH key of type '" . $KeyVal->0 . "' is not supported."); } - } while=([ :len $Keys ] > 0); -} + } +} on-error={ + :global ExitError; $ExitError false $0; +} } diff --git a/mode-button.rsc b/mode-button.rsc index 87e8740..edc5f40 100644 --- a/mode-button.rsc +++ b/mode-button.rsc @@ -1,76 +1,96 @@ #!rsc by RouterOS # RouterOS script: mode-button -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 +# requires device-mode, scheduler # # act on multiple mode and reset button presses -# https://git.eworm.de/cgit/routeros-scripts/about/doc/mode-button.md +# https://rsc.eworm.de/doc/mode-button.md -:local 0 "mode-button"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global ModeButton; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; + + :global ModeButton; -:global LogPrintExit2; + :global LogPrint; -:set ($ModeButton->"count") ($ModeButton->"count" + 1); + :set ($ModeButton->"count") ($ModeButton->"count" + 1); -:local Scheduler [ /system/scheduler/find where name="\$ModeButtonScheduler" ]; + :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; + :if ([ :len $Scheduler ] = 0) do={ + $LogPrint info $ScriptName ("Creating scheduler _ModeButtonScheduler, counting presses..."); + :global ModeButtonScheduler do={ :do { + :local FuncName $0; - :global LogPrintExit2; - :global ModeButtonScheduler; - :global ValidateSyntax; + :global ModeButton; - :local LEDInvert do={ - :global ModeButtonLED; + :global LogPrint; + :global ModeButtonScheduler; + :global ValidateSyntax; - :global IfThenElse; + :local LEDInvert do={ + :global ModeButtonLED; - :local LED [ /system/leds/find where leds=$ModeButtonLED type~"^(on|off)\$" interface=[] ]; - :if ([ :len $LED ] = 0) do={ - :return false; + :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; } - /system/leds/set type=[ $IfThenElse ([ get $LED type ] = "on") "off" "on" ] $LED; - } - :local Count ($ModeButton->"count"); - :local Code ($ModeButton->[ :tostr $Count ]); + :local Count ($ModeButton->"count"); + :local Code ($ModeButton->[ :tostr $Count ]); - :set ($ModeButton->"count") 0; - :set ModeButtonScheduler; - /system/scheduler/remove [ find where name=$0 ]; + :set ($ModeButton->"count") 0; + :set ModeButtonScheduler; + /system/scheduler/remove [ find where name="_ModeButtonScheduler" ]; - :if ([ :len $Code ] > 0) do={ - :if ([ $ValidateSyntax $Code ] = true) do={ - $LogPrintExit2 info $0 ("Acting on " . $Count . " mode-button presses: " . $Code) false; + :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; + :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; } - :delay 200ms; - $LEDInvert; - :delay 200ms; - } - [ :parse $Code ]; + :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={ - $LogPrintExit2 warning $0 ("The code for " . $Count . " mode-button presses failed syntax validation!") false; + $LogPrint info $FuncName ("No action defined for " . $Count . " mode-button presses."); } - } else={ - $LogPrintExit2 info $0 ("No action defined for " . $Count . " mode-button presses.") false; - } + } 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 ]; } - /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 ]; +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } diff --git a/netwatch-dns.rsc b/netwatch-dns.rsc index 119a367..467d636 100644 --- a/netwatch-dns.rsc +++ b/netwatch-dns.rsc @@ -1,93 +1,150 @@ #!rsc by RouterOS # RouterOS script: netwatch-dns -# Copyright (c) 2022-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2022-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.16 +# requires device-mode, fetch # # monitor and manage dns/doh with netwatch -# https://git.eworm.de/cgit/routeros-scripts/about/doc/netwatch-dns.md +# https://rsc.eworm.de/doc/netwatch-dns.md -:local 0 "netwatch-dns"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global CertificateAvailable; -:global EitherOr; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$ScriptLock $0; + :global CertificateAvailable; + :global EitherOr; + :global IsDNSResolving; + :global IsTimeSync; + :global LogPrint; + :global LogPrintOnce; + :global ParseKeyValueStore; + :global ScriptLock; -:if ([ /system/resource/get uptime ] < 5m30s) do={ - $LogPrintExit2 info $0 ("System just booted, giving netwatch some time to settle.") true; -} + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } -:local DnsServers ({}); -:local DnsFallback ({}); -:local DnsCurrent [ /ip/dns/get servers ]; + :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; + } -:foreach Host in=[ /tool/netwatch/find where comment~"dns" status="up" ] do={ - :local HostVal [ /tool/netwatch/get $Host ]; - :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ]; + :local DnsServers ({}); + :local DnsFallback ({}); + :local DnsCurrent [ /ip/dns/get servers ]; - :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"); + :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={ - $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; + :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 DohServer ""; -:local DohCert ""; -:local DohCurrent [ /ip/dns/get use-doh-server ]; -:foreach Host in=[ /tool/netwatch/find where comment~"doh" status="up" ] do={ - :local HostVal [ /tool/netwatch/get $Host ]; - :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ]; + :local DohCurrent [ /ip/dns/get use-doh-server ]; + :local DohServers ({}); - :if ($HostInfo->"doh" = true && $HostInfo->"disabled" != true && $DohServer = "") do={ - :set DohServer [ $EitherOr ($HostInfo->"doh-url") \ - ("https://" . $HostVal->"host" . "/dns-query") ]; - :set DohCert ($HostInfo->"doh-cert"); + :if ([ :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 ""; } -} -:if ($DohServer != "") do={ - :if ($DohServer != $DohCurrent) do={ - $LogPrintExit2 info $0 ("Updating DoH server: " . $DohServer) false; - :if ([ :len $DohCert ] > 0) do={ - /ip/dns/set use-doh-server=""; - :if ([ $CertificateAvailable $DohCert ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading certificate failed, trying without.") false; + :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; } - /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; + + :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.rsc b/netwatch-notify.rsc index e1d489a..0b8a8dc 100644 --- a/netwatch-notify.rsc +++ b/netwatch-notify.rsc @@ -1,188 +1,229 @@ #!rsc by RouterOS # RouterOS script: netwatch-notify -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2020-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # monitor netwatch and send notifications -# https://git.eworm.de/cgit/routeros-scripts/about/doc/netwatch-notify.md +# https://rsc.eworm.de/doc/netwatch-notify.md -:local 0 "netwatch-notify"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global NetwatchNotify; - -:global EitherOr; -:global IfThenElse; -:global IsDNSResolving; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptFromTerminal; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; - -:local NetwatchNotifyHook do={ - :local Name [ :tostr $1 ]; - :local Type [ :tostr $2 ]; - :local State [ :tostr $3 ]; - :local Hook [ :tostr $4 ]; - - :global LogPrintExit2; - :global ValidateSyntax; - - :if ([ $ValidateSyntax $Hook ] = true) do={ - :do { - [ :parse $Hook ]; - } on-error={ - $LogPrintExit2 warning $0 ("The " . $State . "-hook for " . $Type . " '" . $Name . \ - "' failed to run.") false; - :return ("The hook failed to run."); +: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."); } - } else={ - $LogPrintExit2 warning $0 ("The " . $State . "-hook for " . $Type . " '" . $Name . \ - "' failed syntax validation.") false; - :return ("The hook failed syntax validation."); + + $LogPrint info $ScriptName ("Ran hook on " . $Type . " '" . $Name . "' " . $State . ": " . $Hook); + :return ("Ran hook:\n" . $Hook); } - $LogPrintExit2 info $0 ("Ran hook on " . $Type . " '" . $Name . "' " . $State . ": " . \ - $Hook) false; - :return ("Ran hook:\n" . $Hook); -} + :local ResolveExpected do={ + :local ScriptName [ :tostr $1 ]; + :local Name [ :tostr $2 ]; + :local Expected [ :tostr $3 ]; -$ScriptLock $0; + :global GetRandom20CharAlNum; -:local ScriptFromTerminalCached [ $ScriptFromTerminal $0 ]; + :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; + } + } -:if ([ :typeof $NetwatchNotify ] = "nothing") do={ - :set NetwatchNotify ({}); -} + :return false; + } -:foreach Host in=[ /tool/netwatch/find where comment~"notify" !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 ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } - :if ($HostInfo->"notify" = true && $HostInfo->"disabled" != true) do={ - :local Name [ $EitherOr ($HostInfo->"name") ($HostVal->"name") ]; + :local ScriptFromTerminalCached [ $ScriptFromTerminal $ScriptName ]; - :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 $NetwatchNotify ] = "nothing") do={ + :set NetwatchNotify ({}); + } - :if ([ :typeof ($HostInfo->"resolve") ] = "str") do={ - :if ([ $IsDNSResolving ] = true) do={ - :do { - :local Resolve [ :resolve ($HostInfo->"resolve") ]; - :if ($Resolve != $HostVal->"host") do={ - $LogPrintExit2 info $0 ("Name '" . $HostInfo->"resolve" . [ $IfThenElse \ - ($HostInfo->"resolve" != $HostInfo->"name") ("' for " . $Type . " '" . \ - $HostInfo->"name") "" ] . "' resolves to different address " . $Resolve . \ - ", updating.") false; - /tool/netwatch/set host=$Resolve $Host; - :set ($Metric->"resolve-failcnt") 0; - } - } on-error={ - :set ($Metric->"resolve-failcnt") ($Metric->"resolve-failcnt" + 1); - :if ($Metric->"resolve-failcnt" = 3) do={ - $LogPrintExit2 warning $0 ("Resolving name '" . $HostInfo->"resolve" . [ $IfThenElse \ - ($HostInfo->"resolve" != $HostInfo->"name") ("' for " . $Type . " '" . \ - $HostInfo->"name") "" ] . "' failed.") false; + :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={ - $LogPrintExit2 info $0 \ - ("The " . $Type . " '" . $Name . "' (" . $HostDetails . ") is up.") false; - :set ($Metric->"count-down") 0; - } - :set ($Metric->"count-up") ($Metric->"count-up" + 1); - :if ($Metric->"notified" = true) do={ - :local Message ("The " . $Type . " '" . $Name . "' (" . $HostDetails . \ - ") is up since " . $HostVal->"since" . ".\n" . \ - "It was down for " . $CountDown . " checks since " . ($Metric->"since") . "."); - :if ([ :typeof ($HostInfo->"note") ] = "str") do={ - :set Message ($Message . "\n\nNote:\n" . ($HostInfo->"note")); + :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; } - :if ([ :typeof ($HostInfo->"up-hook") ] = "str") do={ - :set Message ($Message . "\n\n" . [ $NetwatchNotifyHook $Name $Type "up" \ - ($HostInfo->"up-hook") ]); + :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") }); } - $SendNotification2 ({ origin=$0; silent=($HostInfo->"silent"); \ - subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Netwatch Notify: " . \ - $Name . " up"); \ - message=$Message }); - } - :set ($Metric->"notified") false; - :set ($Metric->"parent") ($HostInfo->"parent"); - :set ($Metric->"since"); - } else={ - :set ($Metric->"count-down") ($Metric->"count-down" + 1); - :set ($Metric->"count-up") 0; - :set ($Metric->"parent") ($HostInfo->"parent"); - :set ($Metric->"since") ($HostVal->"since"); - :local CountDown [ $IfThenElse ([ :tonum ($HostInfo->"count") ] > 0) ($HostInfo->"count") 5 ]; - :local Parent ($HostInfo->"parent"); - :local ParentUp false; - :while ([ :len $Parent ] > 0) do={ - :set CountDown ($CountDown + 1); - :set Parent ($NetwatchNotify->$Parent->"parent"); + :set ($Metric->"notified") false; + :set ($Metric->"parent") ($HostInfo->"parent"); + :set ($Metric->"since"); } - :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={ + + :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"); } - } - :if ($Metric->"notified" = false || $Metric->"count-down" % 120 = 0 || \ - $ScriptFromTerminalCached = true) do={ - $LogPrintExit2 [ $IfThenElse ($HostInfo->"no-down-notification" != true) info debug ] $0 \ - ("The " . $Type . " '" . $Name . "' (" . $HostDetails . ") is down for " . \ - $Metric->"count-down" . " checks, " . [ $IfThenElse ($ParentNotified = false) [ $IfThenElse \ - ($Metric->"notified" = true) ("already notified.") ($CountDown - $Metric->"count-down" . \ - " to go.") ] ("parent " . $Type . " " . $Parent . " is down.") ]) false; - } - :if ((($CountDown * 2) - ($Metric->"count-down" * 3)) / 2 = 0 && \ - [ :typeof ($HostInfo->"pre-down-hook") ] = "str") do={ - $NetwatchNotifyHook $Name $Type "pre-down" ($HostInfo->"pre-down-hook"); - } - :if ($ParentNotified = false && $Metric->"count-down" >= $CountDown && \ - ($ParentUp = false || $ParentUp > 2) && $Metric->"notified" != true) do={ - :local Message ("The " . $Type . " '" . $Name . "' (" . $HostDetails . \ - ") is down since " . $HostVal->"since" . "."); - :if ([ :typeof ($HostInfo->"note") ] = "str") do={ - :set Message ($Message . "\n\nNote:\n" . ($HostInfo->"note")); + :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 ([ :typeof ($HostInfo->"down-hook") ] = "str") do={ - :set Message ($Message . "\n\n" . [ $NetwatchNotifyHook $Name $Type "down" \ - ($HostInfo->"down-hook") ]); + :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 ($HostInfo->"no-down-notification" != true) do={ - $SendNotification2 ({ origin=$0; silent=($HostInfo->"silent"); \ - subject=([ $SymbolForNotification "cross-mark" ] . "Netwatch Notify: " . \ - $Name . " down"); \ - message=$Message }); + :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 ($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") }; } - :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/news-and-changes.rsc b/news-and-changes.rsc index b027fb6..a9a2904 100644 --- a/news-and-changes.rsc +++ b/news-and-changes.rsc @@ -1,11 +1,17 @@ # News, changes and migration by RouterOS Scripts -# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2019-2025 Christian Hesse <mail@eworm.de> +# 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 are in global-config.changes! +# 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 { @@ -19,11 +25,50 @@ 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."; + 136="Introduced script 'check-perpetual-license' to check for license state on CHR."; + 137="Added support to send notifications via Gotify (gotify.net)."; }; # 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\\n\" ] ] > 0) do={ /system/script/set name=\"mod/ssh-keys-import\" ssh-keys-import; \$ScriptInstallUpdate; }"; + 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.rsc b/ospf-to-leds.rsc index 2dc8448..a8662b3 100644 --- a/ospf-to-leds.rsc +++ b/ospf-to-leds.rsc @@ -1,38 +1,49 @@ #!rsc by RouterOS # RouterOS script: ospf-to-leds -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2020-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # visualize ospf instance state via leds -# https://git.eworm.de/cgit/routeros-scripts/about/doc/ospf-to-leds.md +# https://rsc.eworm.de/doc/ospf-to-leds.md -:local 0 "ospf-to-leds"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; - -$ScriptLock $0; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -: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 ]; + :global LogPrint; + :global ParseKeyValueStore; + :global ScriptLock; - :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 ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; } - :if ($NeighborCount > 0 && $LEDType = "off") do={ - $LogPrintExit2 info $0 ("OSPF instance " . $InstanceVal->"name" . " has " . $NeighborCount . " neighbors, led on!") false; - /system/leds/set type=on [ find where leds=$LED ]; - } - :if ($NeighborCount = 0 && $LEDType = "on") do={ - $LogPrintExit2 info $0 ("OSPF instance " . $InstanceVal->"name" . " has no neighbors, led off!") false; - /system/leds/set type=off [ find where leds=$LED ]; + :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.rsc b/packages-update.rsc index caaac6a..b11596e 100644 --- a/packages-update.rsc +++ b/packages-update.rsc @@ -1,107 +1,168 @@ #!rsc by RouterOS # RouterOS script: packages-update -# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2019-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 +# requires device-mode, scheduler # # download packages and reboot for installation -# https://git.eworm.de/cgit/routeros-scripts/about/doc/packages-update.md +# https://rsc.eworm.de/doc/packages-update.md -:local 0 "packages-update"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global DownloadPackage; -:global Grep; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptFromTerminal; -:global ScriptLock; -:global VersionToNum; - -$ScriptLock $0; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:local Update [ /system/package/update/get ]; + :global BackupRandomDelay; + :global PackagesUpdateDeferReboot; + :global PackagesUpdateBackupFailure; -:if ([ :typeof ($Update->"latest-version") ] = "nothing") do={ - $LogPrintExit2 warning $0 ("Latest version is not known.") true; -} + :global DownloadPackage; + :global Grep; + :global LogPrint; + :global ParseKeyValueStore; + :global ScriptFromTerminal; + :global ScriptLock; + :global VersionToNum; -:if ($Update->"installed-version" = $Update->"latest-version") do={ - $LogPrintExit2 info $0 ("Version " . $Update->"latest-version" . " is already installed.") true; -} + :local Schedule do={ + :local ScriptName [ :tostr $1 ]; -:local NumInstalled [ $VersionToNum ($Update->"installed-version") ]; -:local NumLatest [ $VersionToNum ($Update->"latest-version") ]; + :global GetRandomNumber; + :global LogPrint; -: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..."; + :global RebootForUpdate do={ + /system/reboot; } - } else={ - $LogPrintExit2 warning $0 ("Not installing downgrade automatically.") true; + + :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; } -} -: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; + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; } -} -: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, ") ] ]; + :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; + } - :set ($RunOrder->($Store->"order" . "-" . $ScriptVal->"name")) ($ScriptVal->"name"); -} + :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; + } + } + } -:foreach Order,Script in=$RunOrder do={ - :do { - $LogPrintExit2 info $0 ("Running backup script " . $Script . " before update.") false; - /system/script/run $Script; - } on-error={ - $LogPrintExit2 warning $0 ("Running backup script " . $Script . " before update failed!") false; - :if ([ $ScriptFromTerminal $0 ] = true) do={ - :put "Do you want to continue anyway? [y/N]"; + :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={ - $LogPrintExit2 info $0 ("User requested to continue anyway.") false; + :set DoDowngrade true; } else={ - $LogPrintExit2 info $0 ("Canceled update...") true; + :put "Canceled..."; } } else={ - $LogPrintExit2 info $0 ("Canceled non-interactive update.") true; + $LogPrint warning $ScriptName ("Not installing downgrade automatically."); + :set ExitOK true; + :error false; } } -} -:if ($DoDowngrade = true) do={ - $LogPrintExit2 info $0 ("Rebooting for downgrade.") false; - :delay 1s; - /system/package/downgrade; -} + :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 ([ $ScriptFromTerminal $0 ] = true) do={ - :put "Do you want to (s)chedule reboot or (r)eboot now? [s/R]"; - :if (([ /terminal/inkey timeout=60 ] % 32) = 19) do={ - :global RebootForUpdate do={ - :global RandomDelay; - $RandomDelay 3600; - /system/reboot; + :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; } - /system/scheduler/add name="\$RebootForUpdate" start-time=03:00:00 interval=1d \ - on-event=("/system/scheduler/remove \"\\\$RebootForUpdate\"; " . \ - ":global RebootForUpdate; \$RebootForUpdate;"); - $LogPrintExit2 info $0 ("Scheduled reboot for update between 03:00 and 04:00.") true; } -} -$LogPrintExit2 info $0 ("Rebooting for update.") false; -:delay 1s; -/system/reboot; + $LogPrint info $ScriptName ("Rebooting for update."); + :delay 1s; + /system/reboot; +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; +} diff --git a/ppp-on-up.rsc b/ppp-on-up.rsc index ac01c97..e09bd9d 100644 --- a/ppp-on-up.rsc +++ b/ppp-on-up.rsc @@ -1,34 +1,44 @@ #!rsc by RouterOS # RouterOS script: ppp-on-up -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # run scripts on ppp up -# https://git.eworm.de/cgit/routeros-scripts/about/doc/ppp-on-up.md +# https://rsc.eworm.de/doc/ppp-on-up.md -:local 0 "ppp-on-up"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global LogPrintExit2; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:local Interface $interface; + :global LogPrint; -:if ([ :typeof $Interface ] = "nothing") do={ - $LogPrintExit2 error $0 ("This script is supposed to run from ppp on-up script hook.") true; -} + :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 ]; -$LogPrintExit2 info $0 ("PPP interface " . $IntName . " is up.") false; + :local IntName [ /interface/get $Interface name ]; + $LogPrint info $ScriptName ("PPP interface " . $IntName . " is up."); -/ipv6/dhcp-client/release [ find where interface=$IntName !disabled ]; + /ipv6/dhcp-client/release [ find where interface=$IntName !disabled bound ]; -: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; + :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/sms-action.rsc b/sms-action.rsc index f5de11f..3c8307a 100644 --- a/sms-action.rsc +++ b/sms-action.rsc @@ -1,31 +1,41 @@ #!rsc by RouterOS # RouterOS script: sms-action -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # run action on received SMS -# https://git.eworm.de/cgit/routeros-scripts/about/doc/sms-action.md +# https://rsc.eworm.de/doc/sms-action.md -:local 0 "sms-action"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global SmsAction; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:global LogPrintExit2; -:global ValidateSyntax; + :global SmsAction; -:local Action $action; + :global LogPrint; + :global ValidateSyntax; -:if ([ :typeof $Action ] = "nothing") do={ - $LogPrintExit2 error $0 ("This script is supposed to run from SMS hook with action=...") true; -} + :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={ - $LogPrintExit2 warning $0 ("The code for action '" . $Action . "' failed syntax validation!") 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.rsc b/sms-forward.rsc index 7c6a609..8169022 100644 --- a/sms-forward.rsc +++ b/sms-forward.rsc @@ -1,88 +1,101 @@ #!rsc by RouterOS # RouterOS script: sms-forward -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> # Anatoly Bubenkov <bubenkoff@gmail.com> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # forward SMS to e-mail -# https://git.eworm.de/cgit/routeros-scripts/about/doc/sms-forward.md +# https://rsc.eworm.de/doc/sms-forward.md -:local 0 "sms-forward"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global Identity; -:global SmsForwardHooks; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:global IfThenElse; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; -:global ValidateSyntax; -:global WaitFullyConnected; + :global Identity; + :global SmsForwardHooks; -$ScriptLock $0; + :global IfThenElse; + :global LogPrint; + :global LogPrintOnce; + :global ScriptLock; + :global SendNotification2; + :global SymbolForNotification; + :global ValidateSyntax; + :global WaitFullyConnected; -:if ([ /tool/sms/get receive-enabled ] = false) do={ - $LogPrintExit2 warning $0 ("Receiving of SMS is not enabled.") true; -} + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } -$WaitFullyConnected; + :if ([ /tool/sms/get receive-enabled ] = false) do={ + $LogPrintOnce warning $ScriptName ("Receiving of SMS is not enabled."); + :set ExitOK true; + :error false; + } -:local Settings [ /tool/sms/get ]; + $WaitFullyConnected; -:if ([ /interface/lte/get ($Settings->"port") running ] != true) do={ - $LogPrintExit2 info $0 ("The LTE interface is not in running state, skipping.") true; -} + :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 ({}); + :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 ]; + :foreach Sms in=[ /tool/sms/inbox/find where phone=$Phone ] do={ + :local SmsVal [ /tool/sms/inbox/get $Sms ]; - :if ($Phone = $Settings->"allowed-number" && \ - ($SmsVal->"message")~("^:cmd " . $Settings->"secret" . " script ")) do={ - $LogPrintExit2 debug $0 ("Removing SMS, which started a script.") false; - /tool/sms/inbox/remove $Sms; - } else={ - :set Messages ($Messages . "\n\nOn " . $SmsVal->"timestamp" . \ - " type " . $SmsVal->"type" . ":\n" . $SmsVal->"message"); - :foreach Hook in=$SmsForwardHooks do={ - :if ($Phone~($Hook->"allowed-number") && ($SmsVal->"message")~($Hook->"match")) do={ - :if ([ $ValidateSyntax ($Hook->"command") ] = true) do={ - $LogPrintExit2 info $0 ("Running hook '" . $Hook->"match" . "': " . \ - $Hook->"command") false; - :do { - [ :parse ($Hook->"command") ]; - :set Messages ($Messages . "\n\nRan hook '" . $Hook->"match" . "':\n" . \ - $Hook->"command"); - } on-error={ - $LogPrintExit2 warning $0 ("The code for hook '" . $Hook->"match" . \ - "' failed to run!") false; + :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!"); } - } else={ - $LogPrintExit2 warning $0 ("The code for hook '" . $Hook->"match" . \ - "' failed syntax validation!") false; } } + :set Delete ($Delete, $Sms); } - :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; + :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/super-mario-theme.rsc b/super-mario-theme.rsc index 7787a12..726c526 100644 --- a/super-mario-theme.rsc +++ b/super-mario-theme.rsc @@ -1,10 +1,10 @@ #!rsc by RouterOS # RouterOS script: super-mario-theme -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md # # play Super Mario theme -# https://git.eworm.de/cgit/routeros-scripts/about/doc/super-mario-theme.md +# https://rsc.eworm.de/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 index 62a6ccc..5db4860 100644 --- a/telegram-chat.rsc +++ b/telegram-chat.rsc @@ -1,150 +1,195 @@ #!rsc by RouterOS # RouterOS script: telegram-chat -# Copyright (c) 2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2023-2025 Christian Hesse <mail@eworm.de> +# 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://git.eworm.de/cgit/routeros-scripts/about/doc/telegram-chat.md +# https://rsc.eworm.de/doc/telegram-chat.md -:local 0 "telegram-chat"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global Identity; -:global TelegramChatActive; -:global TelegramChatGroups; -:global TelegramChatId; -:global TelegramChatIdsTrusted; -:global TelegramChatOffset; -:global TelegramChatRunTime; -:global TelegramTokenId; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -:global CertificateAvailable; -:global EitherOr; -:global EscapeForRegEx; -:global GetRandom20CharAlNum; -:global IfThenElse; -:global LogPrintExit2; -:global MkDir; -:global ScriptLock; -:global SendTelegram2; -:global SymbolForNotification; -:global ValidateSyntax; -:global WaitForFile; -:global WaitFullyConnected; + :global Identity; + :global TelegramChatActive; + :global TelegramChatGroups; + :global TelegramChatId; + :global TelegramChatIdsTrusted; + :global TelegramChatOffset; + :global TelegramChatRunTime; + :global TelegramMessageIDs; + :global TelegramRandomDelay; + :global TelegramTokenId; -$ScriptLock $0; + :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; -$WaitFullyConnected; + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; + } -:if ([ :typeof $TelegramChatOffset ] != "array") do={ - :set TelegramChatOffset { 0; 0; 0 }; -} + $WaitFullyConnected; -:if ([ $CertificateAvailable "Go Daddy Secure Certificate Authority - G2" ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true; -} + :if ([ :typeof $TelegramChatOffset ] != "array") do={ + :set TelegramChatOffset { 0; 0; 0 }; + } + :if ([ :typeof $TelegramRandomDelay ] != "num") do={ + :set TelegramRandomDelay 0; + } -:local JsonGetKey do={ - :local Array [ :toarray $1 ]; - :local Key [ :tostr $2 ]; + :if ([ $CertificateAvailable "Go Daddy Root Certificate Authority - G2" ] = false) do={ + $LogPrint warning $ScriptName ("Downloading required certificate failed."); + :set ExitOK true; + :error false; + } - :for I from=0 to=([ :len $Array ] - 1) do={ - :if (($Array->$I) = $Key) do={ - :if ($Array->($I + 1) = ":") do={ - :return ($Array->($I + 2)); + $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"); + } } - :return [ :pick ($Array->($I + 1)) 1 [ :len ($Array->($I + 1)) ] ]; } } - :return false; -} + :if ($Data = false) do={ + $LogPrint warning $ScriptName ("Failed getting updates."); + :set ExitOK true; + :error false; + } -:local Data; -:do { - :set Data ([ /tool/fetch check-certificate=yes-without-crl output=user \ - ("https://api.telegram.org/bot" . $TelegramTokenId . "/getUpdates?offset=" . \ - $TelegramChatOffset->0 . "&allowed_updates=%5B%22message%22%5D") as-value ]->"data"); - :set Data [ :pick $Data ([ :find $Data "[" ] + 1) ([ :len $Data ] - 2) ]; -} on-error={ - $LogPrintExit2 debug $0 ("Failed getting updates from Telegram.") true; -} + :local 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") "" ]; -:local UpdateID 0; -:local Uptime [ /system/resource/get uptime ]; -:foreach Update in=[ :toarray $Data ] do={ - :set UpdateID [ $JsonGetKey $Update "update_id" ]; - :if (($TelegramChatOffset->0 > 0 || $Uptime > 5m) && $UpdateID >= $TelegramChatOffset->2) do={ - :local Trusted false; - :local Message [ $JsonGetKey $Update "message" ]; - :local MessageId [ $JsonGetKey $Message "message_id" ]; - :local From [ $JsonGetKey $Message "from" ]; - :local FromID [ $JsonGetKey $From "id" ]; - :local FromUserName [ $JsonGetKey $From "username" ]; - :local ChatID [ $JsonGetKey [ $JsonGetKey $Message "chat" ] "id" ]; - :local Text [ $JsonGetKey $Message "text" ]; - :foreach IdsTrusted in=($TelegramChatId, $TelegramChatIdsTrusted) do={ - :if ($FromID = $IdsTrusted || $FromUserName = $IdsTrusted) do={ - :set Trusted true; + :foreach IdsTrusted in=($TelegramChatId, $TelegramChatIdsTrusted) do={ + :if ($From->"id" = $IdsTrusted || $From->"username" = $IdsTrusted) do={ + :set Trusted true; + } } - } - :if ($Trusted = true) do={ - :if ([ :pick $Text 0 1 ] = "!") do={ - :if ($Text ~ ("^! *(" . [ $EscapeForRegEx $Identity ] . "|@" . $TelegramChatGroups . ")\$")) do={ - :set TelegramChatActive true; - } else={ - :set TelegramChatActive false; + :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; } - $LogPrintExit2 info $0 ("Now " . [ $IfThenElse $TelegramChatActive "active" "passive" ] . \ - " from update " . $UpdateID . "!") false; - } else={ - :if ($TelegramChatActive = true && $Text != false && [ :len $Text ] > 0) do={ - :if ([ $ValidateSyntax $Text ] = true) do={ + :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 ]); - $MkDir "tmpfs/telegram-chat"; - $LogPrintExit2 info $0 ("Running command from update " . $UpdateID . ": " . $Text) false; - :execute script=(":do {\n" . $Text . "\n} on-error={ /file/add name=\"" . $File . ".failed\" };" . \ - "/file/add name=\"" . $File . ".done\"") file=$File; + :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 "The command did not finish, still running in background.\n\n"; + :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 "The command failed with an error!\n\n"; + :set State ([ $SymbolForNotification "cross-mark" ] . "The command failed with an error!\n\n"); } - :local Content [ /file/get ($File . ".txt") contents ]; - $SendTelegram2 ({ origin=$0; chatid=$ChatID; silent=false; replyto=$MessageId; \ + :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=("Command:\n" . $Text . "\n\n" . $State . [ $IfThenElse ([ :len $Content ] > 0) \ - ("Output:\n" . $Content) [ $IfThenElse ([ /file/get ($File . ".txt") size ] > 0) \ - ("Output exceeds file read size.") ("No output.") ] ]) }); - /file/remove "tmpfs/telegram-chat"; + 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={ - $LogPrintExit2 info $0 ("The command from update " . $UpdateID . " failed syntax validation!") false; - $SendTelegram2 ({ origin=$0; chatid=$ChatID; silent=false; replyto=$MessageId; \ + $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=("Command:\n" . $Text . "\n\nThe command failed syntax validation!") }); + message=([ $SymbolForNotification "gear" ] . "Command:\n" . $Command . "\n\n" . \ + [ $SymbolForNotification "cross-mark" ] . "The command failed syntax validation!") }); } } - } - } else={ - :local Message ("Received a message from untrusted contact " . \ - [ $IfThenElse ($FromUserName = false) "without username" ("'" . $FromUserName . "'") ] . \ - " (ID " . $FromID . ") in update " . $UpdateID . "!"); - :if ($Text ~ ("^! *" . [ $EscapeForRegEx $Identity ] . "\$")) do={ - $LogPrintExit2 warning $0 $Message false; - $SendTelegram2 ({ origin=$0; chatid=$ChatID; silent=false; replyto=$MessageId; \ - subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \ - message=("You are not trusted.") }); } else={ - $LogPrintExit2 info $0 $Message false; + :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 . "."); } - } else={ - $LogPrintExit2 debug $0 ("Already handled update " . $UpdateID . ".") false; } + :set TelegramChatOffset ([ :pick $TelegramChatOffset 1 3 ], \ + [ $IfThenElse ($UpdateID >= $TelegramChatOffset->2) ($UpdateID + 1) ($TelegramChatOffset->2) ]); +} on-error={ + :global ExitError; $ExitError $ExitOK [ :jobname ]; } -:set TelegramChatOffset ([ :pick $TelegramChatOffset 1 3 ], \ - [ $IfThenElse ($UpdateID >= $TelegramChatOffset->2) ($UpdateID + 1) ($TelegramChatOffset->2) ]); diff --git a/unattended-lte-firmware-upgrade.rsc b/unattended-lte-firmware-upgrade.rsc index eac65c3..83925fd 100644 --- a/unattended-lte-firmware-upgrade.rsc +++ b/unattended-lte-firmware-upgrade.rsc @@ -1,16 +1,19 @@ #!rsc by RouterOS # RouterOS script: unattended-lte-firmware-upgrade -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2018-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 +# requires device-mode, scheduler # # schedule unattended lte firmware upgrade -# https://git.eworm.de/cgit/routeros-scripts/about/doc/unattended-lte-firmware-upgrade.md +# 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 once as-value ]; + :set Firmware [ /interface/lte/firmware-upgrade $Interface as-value ]; } on-error={ :log debug ("Could not get latest LTE firmware version for interface " . $IntName . "."); } @@ -24,13 +27,17 @@ :set LTEFirmwareUpgrade; /system/scheduler/remove ($1 . "-firmware-upgrade"); - /interface/lte/firmware-upgrade $1 upgrade=yes; - :log info ("LTE firmware upgrade on '" . $1 . "' finished, waiting for reset."); - :delay 240s; - :local Firmware [ /interface/lte/firmware-upgrade $1 once as-value ]; - :if (($Firmware->"installed") != ($Firmware->"latest")) do={ - :log warning ("LTE firmware versions still differ. Resetting again..."); - /interface/lte/at-chat $1 input="AT+RESET"; + :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."); } } diff --git a/update-gre-address.rsc b/update-gre-address.rsc index e38839c..cddfa92 100644 --- a/update-gre-address.rsc +++ b/update-gre-address.rsc @@ -1,35 +1,46 @@ #!rsc by RouterOS # RouterOS script: update-gre-address -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> +# https://rsc.eworm.de/COPYING.md +# +# requires RouterOS, version=7.15 # # 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 +# https://rsc.eworm.de/doc/update-gre-address.md -:local 0 "update-gre-address"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global CharacterReplace; -:global LogPrintExit2; -:global ScriptLock; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; + + :global CharacterReplace; + :global LogPrint; + :global ScriptLock; -$ScriptLock $0; + :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 ]; + /interface/gre/set remote-address=0.0.0.0 disabled=yes [ find where !running !disabled ]; -:foreach Peer in=[ /ip/ipsec/active-peers/find ] do={ - :local PeerVal [ /ip/ipsec/active-peers/get $Peer ]; - :local GreInt [ /interface/gre/find where comment=($PeerVal->"id") or comment=[ $CharacterReplace ($PeerVal->"id") "CN=" "" ] ]; - :if ([ :len $GreInt ] > 0) do={ - :local GreIntVal [ /interface/gre/get $GreInt ]; - :if ([ :typeof ($PeerVal->"dynamic-address") ] = "str" && \ - ($PeerVal->"dynamic-address" != $GreIntVal->"remote-address" || \ - $GreIntVal->"disabled" = true)) do={ - $LogPrintExit2 info $0 ("Updating remote address for interface " . $GreIntVal->"name" . " to " . $PeerVal->"dynamic-address") false; - /interface/gre/set remote-address=0.0.0.0 disabled=yes [ find where remote-address=$PeerVal->"dynamic-address" name!=$GreIntVal->"name" ]; - /interface/gre/set $GreInt remote-address=($PeerVal->"dynamic-address") disabled=no; + :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.rsc b/update-tunnelbroker.rsc index 60cb003..45afa6f 100644 --- a/update-tunnelbroker.rsc +++ b/update-tunnelbroker.rsc @@ -1,59 +1,74 @@ #!rsc by RouterOS # RouterOS script: update-tunnelbroker -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de> # Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# 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://git.eworm.de/cgit/routeros-scripts/about/doc/update-tunnelbroker.md +# https://rsc.eworm.de/doc/update-tunnelbroker.md -:local 0 "update-tunnelbroker"; :global GlobalFunctionsReady; :while ($GlobalFunctionsReady != true) do={ :delay 500ms; } -:global CertificateAvailable; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; +:local ExitOK false; +:do { + :local ScriptName [ :jobname ]; -$ScriptLock $0; + :global CertificateAvailable; + :global LogPrint; + :global ParseKeyValueStore; + :global ScriptLock; -:if ([ $CertificateAvailable "Starfield Secure Certificate Authority - G2" ] = false) do={ - $LogPrintExit2 error $0 ("Downloading required certificate failed.") true; -} - -:foreach Interface in=[ /interface/6to4/find where comment~"^tunnelbroker" !disabled ] do={ - :local 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={ - $LogPrintExit2 debug $0 ("Failed downloading, " . $I . " retries pending.") false; - :delay 2s; - } - } + :if ([ $ScriptLock $ScriptName ] = false) do={ + :set ExitOK true; + :error false; } - :if (!($Data ~ "^(good|nochg) ")) do={ - $LogPrintExit2 error $0 ("Failed sending the local address to tunnelbroker or unexpected response!") true; + :if ([ $CertificateAvailable "Starfield Root Certificate Authority - G2" ] = false) do={ + $LogPrint error $ScriptName ("Downloading required certificate failed."); + :set ExitOK true; + :error false; } - :local PublicAddress [ :pick $Data ([ :find $Data " " ] + 1) [ :find $Data "\n" ] ]; + :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") ]; - :if ($PublicAddress != $InterfaceVal->"local-address") do={ - :if ([ :len [ /ip/address find where address~("^" . $PublicAddress . "/") ] ] < 1) do={ - $LogPrintExit2 warning $0 ("The address " . $PublicAddress . " is not configured on your device. NAT by ISP?") false; + :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; + } + } } - $LogPrintExit2 info $0 ("Local address changed, updating tunnel configuration with address: " . $PublicAddress) false; - /interface/6to4/set $Interface local-address=$PublicAddress; + :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 ]; } |