aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore10
-rw-r--r--BRANCHES.md50
-rw-r--r--CONTRIBUTIONS.md38
-rw-r--r--INITIAL-COMMANDS.md62
-rw-r--r--Makefile33
-rw-r--r--README.d/01-download-certs.avifbin0 -> 4420 bytes
-rw-r--r--README.d/02-import-certs.avifbin0 -> 3606 bytes
-rw-r--r--README.d/03-check-certs.avifbin0 -> 9339 bytes
-rw-r--r--README.d/04-import-scripts.avifbin0 -> 3782 bytes
-rw-r--r--README.d/05-run-and-schedule-scripts.avifbin0 -> 1946 bytes
-rw-r--r--README.d/06-schedule-update.avifbin0 -> 2200 bytes
-rw-r--r--README.d/07-edit-global-config-overlay.avifbin0 -> 5103 bytes
-rw-r--r--README.d/08-apply-configuration.avifbin0 -> 2437 bytes
-rw-r--r--README.d/09-update-scripts.avifbin0 -> 1733 bytes
-rw-r--r--README.d/10-install-scripts.avifbin0 -> 2423 bytes
-rw-r--r--README.d/11-schedule-script.avifbin0 -> 1847 bytes
-rw-r--r--README.d/12-setup-lease-script.avifbin0 -> 1686 bytes
-rw-r--r--README.d/13-install-custom-script.avifbin0 -> 4589 bytes
-rw-r--r--README.d/14-remove-script.avifbin0 -> 1093 bytes
-rw-r--r--README.d/hello-world.rsc3
-rw-r--r--README.d/notification-news-and-changes.avifbin0 -> 14861 bytes
-rw-r--r--README.d/telegram-group.avifbin0 -> 891 bytes
-rw-r--r--README.d/upstream.pngbin0 -> 207 bytes
-rw-r--r--README.md266
-rw-r--r--accesslist-duplicates.capsman40
-rw-r--r--accesslist-duplicates.capsman.rsc34
-rw-r--r--accesslist-duplicates.local40
-rw-r--r--accesslist-duplicates.local.rsc34
-rw-r--r--accesslist-duplicates.template41
-rw-r--r--accesslist-duplicates.template.rsc43
-rw-r--r--accesslist-duplicates.wifi.rsc34
-rw-r--r--backup-cloud.rsc85
-rw-r--r--backup-email.rsc124
-rw-r--r--backup-partition.rsc57
-rw-r--r--backup-upload.rsc161
-rw-r--r--bridge-port-to-default53
-rw-r--r--bridge-port-toggle21
-rw-r--r--capsman-download-packages62
-rw-r--r--capsman-download-packages.capsman.rsc83
-rw-r--r--capsman-download-packages.template.rsc94
-rw-r--r--capsman-download-packages.wifi.rsc85
-rw-r--r--capsman-rolling-upgrade32
-rw-r--r--capsman-rolling-upgrade.capsman.rsc46
-rw-r--r--capsman-rolling-upgrade.template.rsc54
-rw-r--r--capsman-rolling-upgrade.wifi.rsc47
-rw-r--r--certificate-renew-issued38
-rw-r--r--certificate-renew-issued.rsc48
-rw-r--r--certs/Cloudflare-Inc-ECC-CA-3.pem (renamed from certs/Cloudflare Inc ECC CA-3.pem)71
-rw-r--r--certs/DigiCert-Global-G2-TLS-RSA-SHA256-2020-CA1.pem182
-rw-r--r--certs/DigiCert-TLS-Hybrid-ECC-SHA384-2020-CA1.pem (renamed from certs/DigiCert ECC Secure Server CA.pem)110
-rw-r--r--certs/E1.pem119
-rw-r--r--certs/GTS CA 1O1.pem186
-rw-r--r--certs/GTS-CA-1C3.pem242
-rw-r--r--certs/GTS-CA-1P5.pem238
-rw-r--r--certs/GlobalSign-Atlas-R3-DV-TLS-CA-2022-Q3.pem177
-rw-r--r--certs/Go-Daddy-Secure-Certificate-Authority-G2.pem (renamed from certs/Go Daddy Secure Certificate Authority - G2.pem)0
-rw-r--r--certs/R3.pem77
-rw-r--r--certs/Starfield-Secure-Certificate-Authority-G2.pem (renamed from certs/Starfield Secure Certificate Authority - G2.pem)0
-rw-r--r--check-certificates132
-rw-r--r--check-certificates.rsc222
-rw-r--r--check-health99
-rw-r--r--check-health.rsc178
-rw-r--r--check-lte-firmware-upgrade45
-rw-r--r--check-lte-firmware-upgrade.rsc103
-rw-r--r--check-routeros-update130
-rw-r--r--check-routeros-update.rsc166
-rw-r--r--cloud-backup54
-rw-r--r--collect-wireless-mac.capsman74
-rw-r--r--collect-wireless-mac.capsman.rsc96
-rw-r--r--collect-wireless-mac.local74
-rw-r--r--collect-wireless-mac.local.rsc97
-rw-r--r--collect-wireless-mac.template76
-rw-r--r--collect-wireless-mac.template.rsc114
-rw-r--r--collect-wireless-mac.wifi.rsc96
-rw-r--r--contrib/logo-color.d/browser-01.avifbin0 -> 42058 bytes
-rw-r--r--contrib/logo-color.d/browser-02.avifbin0 -> 29025 bytes
-rw-r--r--contrib/logo-color.d/browser-03.avifbin0 -> 26034 bytes
-rw-r--r--contrib/logo-color.d/script.js12
-rw-r--r--contrib/logo-color.d/style.css5
-rw-r--r--contrib/logo-color.html40
-rw-r--r--contrib/notification.d/script.js6
-rw-r--r--contrib/notification.d/style.css36
-rw-r--r--contrib/notification.html35
-rw-r--r--daily-psk.capsman93
-rw-r--r--daily-psk.capsman.rsc92
-rw-r--r--daily-psk.local93
-rw-r--r--daily-psk.local.rsc91
-rw-r--r--daily-psk.template99
-rw-r--r--daily-psk.template.rsc107
-rw-r--r--daily-psk.wifi.rsc92
-rw-r--r--dhcp-lease-comment.capsman28
-rw-r--r--dhcp-lease-comment.capsman.rsc39
-rw-r--r--dhcp-lease-comment.local28
-rw-r--r--dhcp-lease-comment.local.rsc39
-rw-r--r--dhcp-lease-comment.template29
-rw-r--r--dhcp-lease-comment.template.rsc44
-rw-r--r--dhcp-lease-comment.wifi.rsc39
-rw-r--r--dhcp-to-dns78
-rw-r--r--dhcp-to-dns.rsc126
-rw-r--r--doc/accesslist-duplicates.d/01-example.avifbin0 -> 5208 bytes
-rw-r--r--doc/accesslist-duplicates.md40
-rw-r--r--doc/backup-cloud.d/notification.avifbin0 -> 11629 bytes
-rw-r--r--doc/backup-cloud.md76
-rw-r--r--doc/backup-email.md68
-rw-r--r--doc/backup-partition.md61
-rw-r--r--doc/backup-upload.d/notification.avifbin0 -> 11776 bytes
-rw-r--r--doc/backup-upload.md92
-rw-r--r--doc/bridge-port.md78
-rw-r--r--doc/capsman-download-packages.md62
-rw-r--r--doc/capsman-rolling-upgrade.md30
-rw-r--r--doc/certificate-renew-issued.md22
-rw-r--r--doc/check-certificates.d/notification.avifbin0 -> 25274 bytes
-rw-r--r--doc/check-certificates.md65
-rw-r--r--doc/check-health.d/notification-01-cpu-utilization-high.avifbin0 -> 6481 bytes
-rw-r--r--doc/check-health.d/notification-02-cpu-utilization-ok.avifbin0 -> 6797 bytes
-rw-r--r--doc/check-health.d/notification-03-ram-utilization-high.avifbin0 -> 7527 bytes
-rw-r--r--doc/check-health.d/notification-04-ram-utilization-ok.avifbin0 -> 6637 bytes
-rw-r--r--doc/check-health.d/notification-05-voltage.avifbin0 -> 3829 bytes
-rw-r--r--doc/check-health.d/notification-06-temperature-high.avifbin0 -> 3519 bytes
-rw-r--r--doc/check-health.d/notification-07-temperature-ok.avifbin0 -> 3727 bytes
-rw-r--r--doc/check-health.d/notification-08-psu-fail.avifbin0 -> 3474 bytes
-rw-r--r--doc/check-health.d/notification-09-psu-ok.avifbin0 -> 3531 bytes
-rw-r--r--doc/check-health.md71
-rw-r--r--doc/check-lte-firmware-upgrade.d/notification.avifbin0 -> 5077 bytes
-rw-r--r--doc/check-lte-firmware-upgrade.md27
-rw-r--r--doc/check-routeros-update.d/notification.avifbin0 -> 6392 bytes
-rw-r--r--doc/check-routeros-update.md75
-rw-r--r--doc/cloud-backup.md48
-rw-r--r--doc/collect-wireless-mac.d/notification.avifbin0 -> 13378 bytes
-rw-r--r--doc/collect-wireless-mac.md39
-rw-r--r--doc/daily-psk.d/notification.avifbin0 -> 7040 bytes
-rw-r--r--doc/daily-psk.md67
-rw-r--r--doc/dhcp-lease-comment.md29
-rw-r--r--doc/dhcp-to-dns.md63
-rw-r--r--doc/early-errors.md11
-rw-r--r--doc/email-backup.md54
-rw-r--r--doc/firmware-upgrade-reboot.md43
-rw-r--r--doc/fw-addr-lists.md128
-rw-r--r--doc/global-wait.md47
-rw-r--r--doc/gps-track.md24
-rw-r--r--doc/hotspot-to-wpa.md112
-rw-r--r--doc/ip-addr-bridge.md17
-rw-r--r--doc/ipsec-to-dns.md57
-rw-r--r--doc/ipv6-update.md33
-rw-r--r--doc/lease-script.md36
-rw-r--r--doc/leds-mode.md23
-rw-r--r--doc/log-forward.d/notification.avifbin0 -> 6178 bytes
-rw-r--r--doc/log-forward.md74
-rw-r--r--doc/mod/bridge-port-to.md88
-rw-r--r--doc/mod/bridge-port-vlan.md92
-rw-r--r--doc/mod/inspectvar.d/inspectvar.avifbin0 -> 2838 bytes
-rw-r--r--doc/mod/inspectvar.md40
-rw-r--r--doc/mod/ipcalc.d/ipcalc.avifbin0 -> 1729 bytes
-rw-r--r--doc/mod/ipcalc.d/ipcalcreturn.avifbin0 -> 1247 bytes
-rw-r--r--doc/mod/ipcalc.md60
-rw-r--r--doc/mod/notification-email.md88
-rw-r--r--doc/mod/notification-matrix.d/01-authenticate.avifbin0 -> 4209 bytes
-rw-r--r--doc/mod/notification-matrix.d/02-join-room.avifbin0 -> 3955 bytes
-rw-r--r--doc/mod/notification-matrix.md129
-rw-r--r--doc/mod/notification-ntfy.md86
-rw-r--r--doc/mod/notification-telegram.d/newbot.avifbin0 -> 35870 bytes
-rw-r--r--doc/mod/notification-telegram.d/setuserpic.avifbin0 -> 38522 bytes
-rw-r--r--doc/mod/notification-telegram.md112
-rw-r--r--doc/mod/scriptrunonce.d/hello-world.rsc3
-rw-r--r--doc/mod/scriptrunonce.d/scriptrunonce.avifbin0 -> 2356 bytes
-rw-r--r--doc/mod/scriptrunonce.md59
-rw-r--r--doc/mod/ssh-keys-import.md73
-rw-r--r--doc/mode-button.md36
-rw-r--r--doc/netwatch-dns.md94
-rw-r--r--doc/netwatch-notify.d/notification-01-down.avifbin0 -> 4193 bytes
-rw-r--r--doc/netwatch-notify.d/notification-02-up.avifbin0 -> 4744 bytes
-rw-r--r--doc/netwatch-notify.md148
-rw-r--r--doc/netwatch-syslog.md37
-rw-r--r--doc/ospf-to-leds.md20
-rw-r--r--doc/packages-update.md50
-rw-r--r--doc/ppp-on-up.md18
-rw-r--r--doc/rotate-ntp.md43
-rw-r--r--doc/sms-action.md22
-rw-r--r--doc/sms-forward.d/notification.avifbin0 -> 4619 bytes
-rw-r--r--doc/sms-forward.md73
-rw-r--r--doc/ssh-keys-import.md35
-rw-r--r--doc/super-mario-theme.md15
-rw-r--r--doc/telegram-chat.d/01-chat-specific.avifbin0 -> 38468 bytes
-rw-r--r--doc/telegram-chat.d/02-chat-all.avifbin0 -> 54713 bytes
-rw-r--r--doc/telegram-chat.d/03-reply.avifbin0 -> 50126 bytes
-rw-r--r--doc/telegram-chat.md153
-rw-r--r--doc/unattended-lte-firmware-upgrade.md16
-rw-r--r--doc/update-gre-address.md20
-rw-r--r--doc/update-tunnelbroker.md23
-rw-r--r--doc/upload-backup.md64
-rw-r--r--early-errors6
-rw-r--r--email-backup75
-rw-r--r--firmware-upgrade-reboot.rsc54
-rw-r--r--fw-addr-lists.d/allow3
-rw-r--r--fw-addr-lists.d/block5
-rw-r--r--fw-addr-lists.d/mikrotik5
-rw-r--r--fw-addr-lists.rsc159
-rw-r--r--global-config161
-rw-r--r--global-config-overlay17
-rw-r--r--global-config-overlay.rsc12
-rw-r--r--global-config.changes60
-rw-r--r--global-config.rsc260
-rw-r--r--global-functions1153
-rw-r--r--global-functions.rsc1578
-rw-r--r--global-wait11
-rw-r--r--global-wait.rsc12
-rw-r--r--gps-track34
-rw-r--r--gps-track.rsc50
-rw-r--r--hotspot-to-wpa40
-rw-r--r--hotspot-to-wpa-cleanup.capsman.rsc74
-rw-r--r--hotspot-to-wpa-cleanup.template.rsc81
-rw-r--r--hotspot-to-wpa-cleanup.wifi.rsc74
-rw-r--r--hotspot-to-wpa.capsman.rsc98
-rw-r--r--hotspot-to-wpa.template.rsc118
-rw-r--r--hotspot-to-wpa.wifi.rsc95
-rw-r--r--ip-addr-bridge18
-rw-r--r--ip-addr-bridge.rsc18
-rw-r--r--ipsec-to-dns.rsc79
-rw-r--r--ipv6-update60
-rw-r--r--ipv6-update.rsc87
-rw-r--r--learn-mac-based-vlan13
-rw-r--r--lease-script53
-rw-r--r--lease-script.rsc59
-rw-r--r--leds-day-mode.rsc (renamed from leds-day-mode)4
-rw-r--r--leds-night-mode.rsc (renamed from leds-night-mode)4
-rw-r--r--leds-toggle-mode13
-rw-r--r--leds-toggle-mode.rsc13
-rw-r--r--log-forward68
-rw-r--r--log-forward.rsc102
-rw-r--r--logo.avifbin0 -> 2001 bytes
-rw-r--r--logo.pngbin0 -> 4428 bytes
-rw-r--r--logo.svg29
-rw-r--r--manage-umts29
-rw-r--r--mod/bridge-port-to.rsc66
-rw-r--r--mod/bridge-port-vlan.rsc75
-rw-r--r--mod/inspectvar.rsc57
-rw-r--r--mod/ipcalc.rsc50
-rw-r--r--mod/notification-email.rsc240
-rw-r--r--mod/notification-matrix.rsc260
-rw-r--r--mod/notification-ntfy.rsc136
-rw-r--r--mod/notification-telegram.rsc188
-rw-r--r--mod/scriptrunonce.rsc50
-rw-r--r--mod/ssh-keys-import.rsc112
-rw-r--r--mode-button76
-rw-r--r--mode-button-event6
-rw-r--r--mode-button-scheduler6
-rw-r--r--mode-button.rsc81
-rw-r--r--netwatch-dns.rsc130
-rw-r--r--netwatch-notify97
-rw-r--r--netwatch-notify.rsc219
-rw-r--r--netwatch-syslog17
-rw-r--r--news-and-changes.rsc60
-rw-r--r--ospf-to-leds28
-rw-r--r--ospf-to-leds.rsc45
-rw-r--r--packages-update93
-rw-r--r--packages-update.rsc145
-rw-r--r--ppp-on-up35
-rw-r--r--ppp-on-up.rsc40
-rw-r--r--rotate-ntp32
-rw-r--r--script-updates6
-rw-r--r--sms-action31
-rw-r--r--sms-action.rsc37
-rw-r--r--sms-forward61
-rw-r--r--sms-forward.rsc95
-rw-r--r--ssh-keys-import11
-rw-r--r--super-mario-theme.rsc (renamed from super-mario-theme)2
-rw-r--r--telegram-chat.rsc180
-rw-r--r--unattended-lte-firmware-upgrade.rsc (renamed from unattended-lte-firmware-upgrade)32
-rw-r--r--update-gre-address31
-rw-r--r--update-gre-address.rsc42
-rw-r--r--update-tunnelbroker46
-rw-r--r--update-tunnelbroker.rsc67
-rw-r--r--upload-backup93
273 files changed, 12411 insertions, 5147 deletions
diff --git a/.gitignore b/.gitignore
index f29d4e8..cf89f87 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,13 @@
+# backup and temporary files
*~
+
+# patches and related files
+*.orig
*.patch
+*.rej
+
+# html files (as generated from markdown)
*.html
+
+# Mac OS X folder settings file
+.DS_Store
diff --git a/BRANCHES.md b/BRANCHES.md
new file mode 100644
index 0000000..0fdbdb4
--- /dev/null
+++ b/BRANCHES.md
@@ -0,0 +1,50 @@
+Installing from branches
+========================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](README.md)
+
+> ⚠️ **Warning**: Living on the edge? Great, read on!
+> If not: Please use the `main` branch and leave this page!
+
+These scripts are developed in a [git](https://git-scm.com/) repository.
+Development and experimental branches are used to provide early access
+for specific changes. You can install scripts from these branches
+for testing.
+
+## Install single script
+
+To install a single script from `next` branch:
+
+ $ScriptInstallUpdate script-name "url-suffix=?h=next";
+
+## Switch existing script
+
+Alternatively switch an existing script to update from `next` branch:
+
+ /system/script/set comment="url-suffix=?h=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 ScriptUpdatesUrlSuffix "?h=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/CONTRIBUTIONS.md b/CONTRIBUTIONS.md
index a78b0df..dff933b 100644
--- a/CONTRIBUTIONS.md
+++ b/CONTRIBUTIONS.md
@@ -1,29 +1,57 @@
Past Contributions
==================
-[◀ Go back to main README](README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
-Thanks a lot for your contributions!
+[⬅️ Go back to main README](README.md)
+
+Thanks a lot for your contributions! ❤️
## Patches
-These persons contributed code. See the git history for details!
+These persons contributed code or documentation. See the git history
+for details!
+* [Anatoly Bubenkov](mailto:bubenkoff@gmail.com) (@bubenkoff)
+* [Ben Harris](mailto:mail@bharr.is) (@bharrisau)
* [Daniel Ziegenberg](mailto:daniel@ziegenberg.at) (@ziegenberg)
* [Michael Gisbers](mailto:michael@gisbers.de) (@mgisbers)
* [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)!
+* Abdul Mannan Abbasi
+* Andrea Ruffini Perico
+* Andrew Cox
* Christoph Boss (@Kampfwurst)
+* Devin Dean (@dd2594gh)
+* Evaldo Gardenal
+* 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
* Reiner Vehrenkamp
+* Richard Österreicher
+* Simon Hitzemann
+* Sunny Chu (@sunnychuchu)
+* Ulrich Wessendorf
+* Zac Kornilakis
---
-[◀ Go back to main README](README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](README.md)
+[⬆️ Go back to top](#top)
diff --git a/INITIAL-COMMANDS.md b/INITIAL-COMMANDS.md
index 9a5d6c8..0de50ae 100644
--- a/INITIAL-COMMANDS.md
+++ b/INITIAL-COMMANDS.md
@@ -1,35 +1,55 @@
Initial commands
================
-[◀ Go back to main README](README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
-These command are inteneded for initial setup. If you are not aware of the
-procedure please follow [the long way in detail](README.md#the-long-way-in-detail).
+[⬅️ Go back to main README](README.md)
+
+> ⚠️ **Warning**: These command are inteneded for initial setup. If you are
+> not aware of the procedure please follow
+> [the long way in detail](README.md#the-long-way-in-detail).
+
+Run the complete base installation:
{
- / tool fetch "https://git.eworm.de/cgit/routeros-scripts/plain/certs/R3.pem" dst-path="letsencrypt-R3.pem";
+ /tool/fetch "https://git.eworm.de/cgit/routeros-scripts/plain/certs/E1.pem" dst-path="letsencrypt-E1.pem" as-value;
:delay 1s;
- / certificate import file-name=letsencrypt-R3.pem passphrase="";
- :if ([ :len [ / certificate find where fingerprint="67add1166b020ae61b8f5fc96813c04c2aa589960796865572a3c7e737613dfd" or fingerprint="96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6" or fingerprint="0687260331a72403d909f105e69bcf0d32e1bd2493ffc6d9206d11bcd6770739" ] ] != 3) do={
+ /certificate/import file-name=letsencrypt-E1.pem passphrase="";
+ :if ([ :len [ /certificate/find where fingerprint="46494e30379059df18be52124305e606fc59070e5b21076ce113954b60517cda" or fingerprint="69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470" ] ] != 2) do={
:error "Something is wrong with your certificates!";
- }
- / file remove "letsencrypt-R3.pem";
+ };
+ /file/remove "letsencrypt-E1.pem";
+ :delay 1s;
+ /system/script/set name=("global-config-overlay-" . [ /system/clock/get date ] . "-" . [ /system/clock/get time ]) [ find where name="global-config-overlay" ];
:foreach Script in={ "global-config"; "global-config-overlay"; "global-functions" } do={
- / system script add name=$Script source=([ / tool fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script) output=user as-value]->"data");
- }
- / system script set comment="ignore" global-config-overlay;
- / system script { run global-config; run global-config-overlay; run global-functions; }
- / system scheduler add name="global-scripts" start-time=startup on-event="/ system script { run global-config; run global-config-overlay; run global-functions; }";
+ /system/script/remove [ find where name=$Script ];
+ /system/script/add name=$Script owner=$Script source=([ /tool/fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script . ".rsc") output=user as-value]->"data");
+ };
+ /system/script { run global-config; run global-functions; };
+ /system/scheduler/remove [ find where name="global-scripts" ];
+ /system/scheduler/add name="global-scripts" start-time=startup on-event="/system/script { run global-config; run global-functions; }";
:global CertificateNameByCN;
- $CertificateNameByCN "R3";
- $CertificateNameByCN "ISRG Root X1";
- $CertificateNameByCN "DST Root CA X3";
- }
+ $CertificateNameByCN "E1";
+ $CertificateNameByCN "ISRG Root X2";
+ };
+
+Then continue setup with
+[scheduled automatic updates](README.md#scheduled-automatic-updates) or
+[editing configuration](README.md#editing-configuration).
-Optional to update the scripts automatically:
+## Fix existing installation
- / system scheduler add name="ScriptInstallUpdate" start-time=startup interval=1d on-event=":global ScriptInstallUpdate; \$ScriptInstallUpdate;";
+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)
+[⬅️ Go back to main README](README.md)
+[⬆️ Go back to top](#top)
diff --git a/Makefile b/Makefile
index f96e73e..d21713c 100644
--- a/Makefile
+++ b/Makefile
@@ -2,24 +2,35 @@
# template scripts -> final scripts
# markdown files -> html files
-TEMPLATE = $(wildcard *.template)
-CAPSMAN = $(TEMPLATE:.template=.capsman)
-LOCAL = $(TEMPLATE:.template=.local)
+CAPSMAN = $(wildcard *.capsman.rsc)
+LOCAL = $(wildcard *.local.rsc)
+WIFI = $(wildcard *.wifi.rsc)
-MARKDOWN = $(wildcard *.md)
-HTML = $(MARKDOWN:.md=.html)
+MARKDOWN = $(wildcard *.md doc/*.md doc/mod/*.md)
+HTML = $(MARKDOWN:.md=.html)
-all: $(CAPSMAN) $(LOCAL) $(HTML)
+all: $(CAPSMAN) $(LOCAL) $(WIFI) $(HTML)
%.html: %.md Makefile
- markdown $< | sed 's/href="\([-[:alnum:]]*\)\.md"/href="\1.html"/g' > $@
+ markdown $< | sed 's/href="\([-_\./[:alnum:]]*\)\.md"/href="\1.html"/g' > $@
-%.local: %.template Makefile
- sed -e '/\/ caps-man/d' -e 's|%PATH%|interface wireless|' -e 's|%TEMPL%|$(suffix $@)|' \
+%.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: %.template Makefile
- sed -e '/\/ interface wireless/d' -e 's/%PATH%/caps-man/' -e 's/%TEMPL%/$(suffix $@)/' \
+%.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!' \
< $< > $@
+
+%.wifi.rsc: %.template.rsc Makefile
+ sed -e '/\/caps-man\//d' -e '/\/interface\/wireless\//d' -e 's|%TEMPL%|.wifi|' \
+ -e '/^# NOT \/interface\/wifi\/ #$$/,/^# NOT \/interface\/wifi\/ #$$/d' \
+ -e '/^# !!/,/^# !!/c # !! Do not edit this file, it is generated from template!' \
+ < $< > $@
+
+clean:
+ rm -f $(HTML)
diff --git a/README.d/01-download-certs.avif b/README.d/01-download-certs.avif
new file mode 100644
index 0000000..b27b23b
--- /dev/null
+++ b/README.d/01-download-certs.avif
Binary files differ
diff --git a/README.d/02-import-certs.avif b/README.d/02-import-certs.avif
new file mode 100644
index 0000000..d42994b
--- /dev/null
+++ b/README.d/02-import-certs.avif
Binary files differ
diff --git a/README.d/03-check-certs.avif b/README.d/03-check-certs.avif
new file mode 100644
index 0000000..33bdc40
--- /dev/null
+++ b/README.d/03-check-certs.avif
Binary files differ
diff --git a/README.d/04-import-scripts.avif b/README.d/04-import-scripts.avif
new file mode 100644
index 0000000..53439e4
--- /dev/null
+++ b/README.d/04-import-scripts.avif
Binary files differ
diff --git a/README.d/05-run-and-schedule-scripts.avif b/README.d/05-run-and-schedule-scripts.avif
new file mode 100644
index 0000000..37e1173
--- /dev/null
+++ b/README.d/05-run-and-schedule-scripts.avif
Binary files differ
diff --git a/README.d/06-schedule-update.avif b/README.d/06-schedule-update.avif
new file mode 100644
index 0000000..7c96f3a
--- /dev/null
+++ b/README.d/06-schedule-update.avif
Binary files differ
diff --git a/README.d/07-edit-global-config-overlay.avif b/README.d/07-edit-global-config-overlay.avif
new file mode 100644
index 0000000..f87fda8
--- /dev/null
+++ b/README.d/07-edit-global-config-overlay.avif
Binary files differ
diff --git a/README.d/08-apply-configuration.avif b/README.d/08-apply-configuration.avif
new file mode 100644
index 0000000..b66af1a
--- /dev/null
+++ b/README.d/08-apply-configuration.avif
Binary files differ
diff --git a/README.d/09-update-scripts.avif b/README.d/09-update-scripts.avif
new file mode 100644
index 0000000..f549fef
--- /dev/null
+++ b/README.d/09-update-scripts.avif
Binary files differ
diff --git a/README.d/10-install-scripts.avif b/README.d/10-install-scripts.avif
new file mode 100644
index 0000000..00225b1
--- /dev/null
+++ b/README.d/10-install-scripts.avif
Binary files differ
diff --git a/README.d/11-schedule-script.avif b/README.d/11-schedule-script.avif
new file mode 100644
index 0000000..27541b7
--- /dev/null
+++ b/README.d/11-schedule-script.avif
Binary files differ
diff --git a/README.d/12-setup-lease-script.avif b/README.d/12-setup-lease-script.avif
new file mode 100644
index 0000000..365e0e8
--- /dev/null
+++ b/README.d/12-setup-lease-script.avif
Binary files differ
diff --git a/README.d/13-install-custom-script.avif b/README.d/13-install-custom-script.avif
new file mode 100644
index 0000000..2f01c43
--- /dev/null
+++ b/README.d/13-install-custom-script.avif
Binary files differ
diff --git a/README.d/14-remove-script.avif b/README.d/14-remove-script.avif
new file mode 100644
index 0000000..a5c7daf
--- /dev/null
+++ b/README.d/14-remove-script.avif
Binary files differ
diff --git a/README.d/hello-world.rsc b/README.d/hello-world.rsc
new file mode 100644
index 0000000..6404781
--- /dev/null
+++ b/README.d/hello-world.rsc
@@ -0,0 +1,3 @@
+#!rsc by RouterOS
+
+:put ("Hello World from " . [ /system/identity/get name ] . "!");
diff --git a/README.d/notification-news-and-changes.avif b/README.d/notification-news-and-changes.avif
new file mode 100644
index 0000000..d91b8a0
--- /dev/null
+++ b/README.d/notification-news-and-changes.avif
Binary files differ
diff --git a/README.d/telegram-group.avif b/README.d/telegram-group.avif
new file mode 100644
index 0000000..eb75d13
--- /dev/null
+++ b/README.d/telegram-group.avif
Binary files differ
diff --git a/README.d/upstream.png b/README.d/upstream.png
new file mode 100644
index 0000000..fd5e877
--- /dev/null
+++ b/README.d/upstream.png
Binary files differ
diff --git a/README.md b/README.md
index 14b32db..614095a 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,14 @@
RouterOS Scripts
================
-[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?style=social)](https://github.com/eworm-de/routeros-scripts/stargazers)
-[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?style=social)](https://github.com/eworm-de/routeros-scripts/network)
-[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?style=social)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+![RouterOS Scripts Logo](logo.svg)
[RouterOS](https://mikrotik.com/software) is the operating system developed
by [MikroTik](https://mikrotik.com/aboutus) for networking tasks. This
@@ -16,11 +21,27 @@ to manage RouterOS devices or extend their functionality.
Requirements
------------
+### Software (RouterOS)
+
Latest version of the scripts require recent RouterOS to function properly.
-Make sure to install latest updates before you begin.
+Make sure to install latest updates before you begin. If new functionality
+or a breaking change in RouterOS `7.n` is used in my scripts I push my
+change some time after `7.(n+1)` was released. At any time you should have
+at least two minor and their bugfix releases to choose from.
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!
+
+### 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
-------------
@@ -38,8 +59,8 @@ 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.
-*Be warned!* Some details changed. So see the presentation, then follow
-the steps below for up-to-date commands.
+> ⚠️ **Warning**: Some details changed. So see the presentation, then follow
+> the steps below for up-to-date commands.
### The long way in detail
@@ -48,76 +69,109 @@ download the certificates. If you intend to download the scripts from a
different location (for example from github.com) install the corresponding
certificate chain.
- [admin@MikroTik] > / tool fetch "https://git.eworm.de/cgit/routeros-scripts/plain/certs/R3.pem" dst-path="letsencrypt-R3.pem"
- status: finished
- downloaded: 4KiBC-z pause]
- total: 4KiB
- duration: 1s
+ /tool/fetch "https://git.eworm.de/cgit/routeros-scripts/plain/certs/E1.pem" dst-path="letsencrypt-E1.pem";
+
+![screenshot: download certs](README.d/01-download-certs.avif)
Note that the commands above do *not* verify server certificate, so if you
want to be safe download with your workstations's browser and transfer the
files to your MikroTik device.
-* [ISRG Root X1](https://letsencrypt.org/certs/isrgrootx1.pem)
-* Let's Encrypt [R3](https://letsencrypt.org/certs/lets-encrypt-r3.pem)
+* [ISRG Root X2](https://letsencrypt.org/certs/isrg-root-x2.pem)
+* Let's Encrypt [E1](https://letsencrypt.org/certs/lets-encrypt-e1.pem)
Then we import the certificates.
- [admin@MikroTik] > / certificate import file-name=letsencrypt-R3.pem passphrase=""
- certificates-imported: 3
- private-keys-imported: 0
- files-imported: 1
- decryption-failures: 0
- keys-with-no-certificate: 0
+ /certificate/import file-name=letsencrypt-E1.pem passphrase="";
-For basic verification we rename the certificates and print their count. Make
-sure the certificate count is **three**.
+Do not worry that the command is not shown - that happens because it contains
+a sensitive property, the passphrase.
- [admin@MikroTik] > / certificate set name="R3" [ find where fingerprint="67add1166b020ae61b8f5fc96813c04c2aa589960796865572a3c7e737613dfd" ]
- [admin@MikroTik] > / certificate set name="ISRG-Root-X1" [ find where fingerprint="96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6" ]
- [admin@MikroTik] > / certificate set name="DST-Root-CA-X3" [ find where fingerprint="0687260331a72403d909f105e69bcf0d32e1bd2493ffc6d9206d11bcd6770739" ]
- [admin@MikroTik] > / certificate print count-only where fingerprint="67add1166b020ae61b8f5fc96813c04c2aa589960796865572a3c7e737613dfd" or fingerprint="96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6" or fingerprint="0687260331a72403d909f105e69bcf0d32e1bd2493ffc6d9206d11bcd6770739"
- 3
+![screenshot: import certs](README.d/02-import-certs.avif)
+
+For basic verification we rename the certificates and print them by
+fingerprint. Make sure exactly these two certificates ("*E1*" and
+"*ISRG-Root-X2*") are shown.
+
+ /certificate/set name="E1" [ find where common-name="E1" ];
+ /certificate/set name="ISRG-Root-X2" [ find where common-name="ISRG Root X2" ];
+ /certificate/print proplist=name where fingerprint="46494e30379059df18be52124305e606fc59070e5b21076ce113954b60517cda" or fingerprint="69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470";
+
+![screenshot: check certs](README.d/03-check-certs.avif)
Always make sure there are no certificates installed you do not know or want!
-Actually we do not require the certificate named `DST Root CA X3`, but as it
-is used by `Let's Encrypt` to cross-sign we install it anyway - this makes
-sure things do not go wrong if the intermediate certificate is replaced.
-The IdenTrust certificate *should* be available from their
-[download page](https://www.identrust.com/support/downloads). The site is
-crap and a good example how to *not* do it.
+All following commands will verify the server certificate. For validity the
+certificate's lifetime is checked with local time, so make sure the device's
+date and time is set correctly!
Now let's download the main scripts and add them in configuration on the fly.
- [admin@MikroTik] > :foreach Script in={ "global-config"; "global-config-overlay"; "global-functions" } do={ / system script add name=$Script source=([ / tool fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script) output=user as-value]->"data"); }
+ :foreach Script in={ "global-config"; "global-config-overlay"; "global-functions" } do={ /system/script/add name=$Script 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"); };
+
+![screenshot: import scripts](README.d/04-import-scripts.avif)
+
+And finally load configuration and functions and add the scheduler.
-Mark `global-config-overlay` not to be overwritten by future updates.
+ /system/script { run global-config; run global-functions; };
+ /system/scheduler/add name="global-scripts" start-time=startup on-event="/system/script { run global-config; run global-functions; }";
- [admin@MikroTik] > / system script set comment="ignore" global-config-overlay
+![screenshot: run and schedule scripts](README.d/05-run-and-schedule-scripts.avif)
+
+### Scheduled automatic updates
+
+The last step is optional: Add this scheduler **only** if you want the
+scripts to be updated automatically!
+
+ /system/scheduler/add name="ScriptInstallUpdate" start-time=startup interval=1d on-event=":global ScriptInstallUpdate; \$ScriptInstallUpdate;";
+
+![screenshot: schedule update](README.d/06-schedule-update.avif)
+
+Editing configuration
+---------------------
The configuration needs to be tweaked for your needs. Edit
-`global-config-overlay`, copy configuration from
-[`global-config`](global-config) (the one without `-overlay`).
+`global-config-overlay`, copy relevant configuration from
+[`global-config`](global-config.rsc) (the one without `-overlay`).
+Save changes and exit with `Ctrl-o`.
- [admin@MikroTik] > / system script edit global-config-overlay source
+ /system/script/edit global-config-overlay source;
-And finally load configuration and functions and add the scheduler.
+![screenshot: edit global-config-overlay](README.d/07-edit-global-config-overlay.avif)
- [admin@MikroTik] > / system script { run global-config; run global-config-overlay; run global-functions; }
- [admin@MikroTik] > / system scheduler add name="global-scripts" start-time=startup on-event="/ system script { run global-config; run global-config-overlay; run global-functions; }"
+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.
-The last step is optional: Add this scheduler **only** if you want the scripts
-to be updated automatically!
+To apply your changes run `global-config`, which will automatically load
+the overlay as well:
- [admin@MikroTik] > / system scheduler add name="ScriptInstallUpdate" start-time=startup interval=1d on-event=":global ScriptInstallUpdate; \$ScriptInstallUpdate;"
+ /system/script/run global-config;
+
+![screenshot: apply configuration](README.d/08-apply-configuration.avif)
+
+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;`
Updating scripts
----------------
-To update existing scripts just run function `$ScriptInstallUpdate`.
+To update existing scripts just run function `$ScriptInstallUpdate`. If
+everything is up-to-date it will not produce any output.
+
+ $ScriptInstallUpdate;
+
+![screenshot: update scripts](README.d/09-update-scripts.avif)
+
+If the update includes news or requires configuration changes a notification
+is sent - in addition to terminal output and log messages.
- [admin@MikroTik] > $ScriptInstallUpdate
+![news and changes notification](README.d/notification-news-and-changes.avif)
Adding a script
---------------
@@ -125,7 +179,9 @@ Adding a script
To add a script from the repository run function `$ScriptInstallUpdate` with
a comma separated list of script names.
- [admin@MikroTik] > $ScriptInstallUpdate check-certificates,check-routeros-update
+ $ScriptInstallUpdate check-certificates,check-routeros-update;
+
+![screenshot: install scripts](README.d/10-install-scripts.avif)
Scheduler and events
--------------------
@@ -135,23 +191,30 @@ Most scripts are designed to run regularly from
added `check-routeros-update`, so let's run it every hour to make sure not to
miss an update.
- [admin@MikroTik] > / 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=1h on-event="/system/script/run check-routeros-update;";
+
+![screenshot: schedule script](README.d/11-schedule-script.avif)
Some events can run a script. If you want your DHCP hostnames to be available
in DNS use `dhcp-to-dns` with the events from dhcp server. For a regular
cleanup add a scheduler entry.
- [admin@MikroTik] > $ScriptInstallUpdate dhcp-to-dns,lease-script
- [admin@MikroTik] > / ip dhcp-server set lease-script=lease-script [ find ]
- [admin@MikroTik] > / system scheduler add name="dhcp-to-dns" interval=5m on-event="/ system script run dhcp-to-dns;"
+ $ScriptInstallUpdate dhcp-to-dns,lease-script;
+ /ip/dhcp-server/set lease-script=lease-script [ find ];
+ /system/scheduler/add name="dhcp-to-dns" interval=5m on-event="/system/script/run dhcp-to-dns;";
+
+![screenshot: setup lease script](README.d/12-setup-lease-script.avif)
There's much more to explore... Have fun!
-Available Scripts
+Available scripts
-----------------
* [Find and remove access list duplicates](doc/accesslist-duplicates.md)
-* [Manage ports in bridge](doc/bridge-port.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)
@@ -159,55 +222,122 @@ Available Scripts
* [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)
-* [Upload backup to Mikrotik cloud](doc/cloud-backup.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)
-* [Send backup via e-mail](doc/email-backup.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)
+* [Use WPA 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)
-* [Manage remote logging](doc/netwatch-syslog.md)
* [Visualize OSPF state via LEDs](doc/ospf-to-leds.md)
* [Manage system update](doc/packages-update.md)
* [Run scripts on ppp connection](doc/ppp-on-up.md)
-* [Rotate NTP servers](doc/rotate-ntp.md)
* [Act on received SMS](doc/sms-action.md)
* [Forward received SMS](doc/sms-forward.md)
-* [Import SSH keys](doc/ssh-keys-import.md)
* [Play Super Mario theme](doc/super-mario-theme.md)
+* [Chat with your router and send commands via Telegram bot](doc/telegram-chat.md)
* [Install LTE firmware upgrade](doc/unattended-lte-firmware-upgrade.md)
* [Update GRE configuration with dynamic addresses](doc/update-gre-address.md)
* [Update tunnelbroker configuration](doc/update-tunnelbroker.md)
-* [Upload backup to server](doc/upload-backup.md)
-[comment]: # (TODO: currently undocumented)
-[comment]: # (* learn-mac-based-vlan)
-[comment]: # (* manage-umts)
+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 Ntfy](doc/mod/notification-ntfy.md)
+* [Send notifications via Telegram](doc/mod/notification-telegram.md)
+* [Download script and run it once](doc/mod/scriptrunonce.md)
+* [Import ssh keys for public key authentication](doc/mod/ssh-keys-import.md)
+
+Installing custom scripts & modules
+-----------------------------------
+
+My scripts cover a lot of use cases, but you may have your own ones. You can
+still use my scripts to manage and deploy yours, by specifying `base-url`
+(and `url-suffix`) for each script.
+
+This will fetch and install a script `hello-world.rsc` from the given url:
+
+ $ScriptInstallUpdate hello-world "base-url=https://git.eworm.de/cgit/routeros-scripts-custom/plain/";
+
+![screenshot: install custom script](README.d/13-install-custom-script.avif)
+
+For a script to be considered valid it has to begin with a *magic token*.
+Have a look at [any script](README.d/hello-world.rsc) and copy the first line
+without modification.
+
+Starting a script's name with `mod/` makes it a module and it is run
+automatically by `global-functions`.
+
+### Linked custom scripts & modules
+
+> ⚠️ **Warning**: These links are being provided for your convenience only;
+> they do not constitute an endorsement or an approval by me. I bear no
+> responsibility for the accuracy, legality or content of the external site
+> or for that of subsequent links. Contact the external site for answers to
+> questions regarding its content.
+
+* [Hello World](https://git.eworm.de/cgit/routeros-scripts-custom/about/doc/hello-world.md)
+ (This is a demo script to show how the linking to external documentation
+ will be done.)
+
+> ℹ️ **Info**: You have your own set of scripts and/or modules and want these
+> to be listed here? There should be a general info page that links here,
+> and documentation for each script. You can start by cloning my
+> [Custom RouterOS-Scripts](https://git.eworm.de/cgit/routeros-scripts-custom/)
+> (or fork on [GitHub](https://github.com/eworm-de/routeros-scripts-custom)
+> or [GitLab](https://gitlab.com/eworm-de/routeros-scripts-custom)) and make
+> your changes. Then please [get in contact](#patches-issues-and-whishlist)...
+
+Removing a script
+-----------------
+
+There is no specific function for script removal. Just remove it from
+configuration...
+
+ /system/script/remove to-be-removed;
+
+![screenshot: remove script](README.d/14-remove-script.avif)
+
+Possibly a scheduler and other configuration has to be removed as well.
Contact
-------
We have a Telegram Group [RouterOS-Scripts](https://t.me/routeros_scripts)!
+
+[![RouterOS Scripts Telegram Group](README.d/telegram-group.avif)](https://t.me/routeros_scripts)
+
Get help, give feedback or just chat - but do not expect free professional
support!
Contribute
----------
-Thanks a lot for [past contributions](CONTRIBUTIONS.md)!
+Thanks a lot for [past contributions](CONTRIBUTIONS.md)! ❤️
### Patches, issues and whishlist
Feel free to contact me via e-mail or open an
-[issue at github](https://github.com/eworm-de/routeros-scripts/issues).
+[issue](https://github.com/eworm-de/routeros-scripts/issues) or
+[pull request](https://github.com/eworm-de/routeros-scripts/pulls)
+at github.
### Donate
@@ -216,7 +346,7 @@ 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.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=for-the-badge)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
Thanks a lot for your support!
@@ -236,6 +366,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Upstream
--------
+![upstream](README.d/upstream.png)
+
URL:
[GitHub.com](https://github.com/eworm-de/routeros-scripts#routeros-scripts)
@@ -244,4 +376,4 @@ Mirror:
[GitLab.com](https://gitlab.com/eworm-de/routeros-scripts#routeros-scripts)
---
-[▲ Go back to top](#top)
+[⬆️ Go back to top](#top)
diff --git a/accesslist-duplicates.capsman b/accesslist-duplicates.capsman
deleted file mode 100644
index 5fcf6ae..0000000
--- a/accesslist-duplicates.capsman
+++ /dev/null
@@ -1,40 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: accesslist-duplicates.capsman
-# Copyright (c) 2018-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# print duplicate antries in wireless access list
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/accesslist-duplicates.md
-#
-# !! Do not edit this file, it is generated from template!
-
-:local 0 "accesslist-duplicates.capsman";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:local Seen [ :toarray "" ];
-:local Shown [ :toarray "" ];
-
-:foreach AccList in=[ / caps-man access-list find where mac-address!="00:00:00:00:00:00" ] do={
- :local Mac [ / caps-man access-list get $AccList mac-address ];
- :foreach SeenMac in=$Seen do={
- :if ($SeenMac = $Mac) do={
- :local Skip 0;
- :foreach ShownMac in=$Shown do={
- :if ($ShownMac = $Mac) do={ :set Skip 1; }
- }
- :if ($Skip = 0) do={
- / caps-man access-list print where mac-address=$Mac;
- :set Shown ($Shown, $Mac);
-
- :put "\nNumeric id to remove, any key to skip!";
- :local Remove ([ :terminal inkey ] - 48);
- :if ($Remove >= 0 && $Remove <= 9) do={
- :put ("Removing numeric id " . $Remove . "...\n");
- / caps-man access-list remove $Remove;
- }
- }
- }
- }
- :set Seen ($Seen, $Mac);
-}
diff --git a/accesslist-duplicates.capsman.rsc b/accesslist-duplicates.capsman.rsc
new file mode 100644
index 0000000..2ce8302
--- /dev/null
+++ b/accesslist-duplicates.capsman.rsc
@@ -0,0 +1,34 @@
+#!rsc by RouterOS
+# RouterOS script: accesslist-duplicates.capsman
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :local Seen ({});
+
+ :foreach AccList in=[ /caps-man/access-list/find where mac-address!="00:00:00:00:00:00" ] do={
+ :local Mac [ /caps-man/access-list/get $AccList mac-address ];
+ :if ($Seen->$Mac = 1) do={
+ /caps-man/access-list/print where mac-address=$Mac;
+ :local Remove [ :tonum [ /terminal/ask prompt="\nNumeric id to remove, any key to skip!" ] ];
+
+ :if ([ :typeof $Remove ] = "num") do={
+ :put ("Removing numeric id " . $Remove . "...\n");
+ /caps-man/access-list/remove $Remove;
+ }
+ }
+ :set ($Seen->$Mac) 1;
+ }
+} on-error={ }
diff --git a/accesslist-duplicates.local b/accesslist-duplicates.local
deleted file mode 100644
index 4d058d4..0000000
--- a/accesslist-duplicates.local
+++ /dev/null
@@ -1,40 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: accesslist-duplicates.local
-# Copyright (c) 2018-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# print duplicate antries in wireless access list
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/accesslist-duplicates.md
-#
-# !! Do not edit this file, it is generated from template!
-
-:local 0 "accesslist-duplicates.local";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:local Seen [ :toarray "" ];
-:local Shown [ :toarray "" ];
-
-:foreach AccList in=[ / interface wireless access-list find where mac-address!="00:00:00:00:00:00" ] do={
- :local Mac [ / interface wireless access-list get $AccList mac-address ];
- :foreach SeenMac in=$Seen do={
- :if ($SeenMac = $Mac) do={
- :local Skip 0;
- :foreach ShownMac in=$Shown do={
- :if ($ShownMac = $Mac) do={ :set Skip 1; }
- }
- :if ($Skip = 0) do={
- / interface wireless access-list print where mac-address=$Mac;
- :set Shown ($Shown, $Mac);
-
- :put "\nNumeric id to remove, any key to skip!";
- :local Remove ([ :terminal inkey ] - 48);
- :if ($Remove >= 0 && $Remove <= 9) do={
- :put ("Removing numeric id " . $Remove . "...\n");
- / interface wireless access-list remove $Remove;
- }
- }
- }
- }
- :set Seen ($Seen, $Mac);
-}
diff --git a/accesslist-duplicates.local.rsc b/accesslist-duplicates.local.rsc
new file mode 100644
index 0000000..51ef6f3
--- /dev/null
+++ b/accesslist-duplicates.local.rsc
@@ -0,0 +1,34 @@
+#!rsc by RouterOS
+# RouterOS script: accesslist-duplicates.local
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :local Seen ({});
+
+ :foreach AccList in=[ /interface/wireless/access-list/find where mac-address!="00:00:00:00:00:00" ] do={
+ :local Mac [ /interface/wireless/access-list/get $AccList mac-address ];
+ :if ($Seen->$Mac = 1) do={
+ /interface/wireless/access-list/print where mac-address=$Mac;
+ :local Remove [ :tonum [ /terminal/ask prompt="\nNumeric id to remove, any key to skip!" ] ];
+
+ :if ([ :typeof $Remove ] = "num") do={
+ :put ("Removing numeric id " . $Remove . "...\n");
+ /interface/wireless/access-list/remove $Remove;
+ }
+ }
+ :set ($Seen->$Mac) 1;
+ }
+} on-error={ }
diff --git a/accesslist-duplicates.template b/accesslist-duplicates.template
deleted file mode 100644
index 8878bcc..0000000
--- a/accesslist-duplicates.template
+++ /dev/null
@@ -1,41 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: accesslist-duplicates%TEMPL%
-# Copyright (c) 2018-2021 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
-#
-# !! This is just a template! Replace '%PATH%' with 'caps-man'
-# !! or 'interface wireless'!
-
-:local 0 "accesslist-duplicates%TEMPL%";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:local Seen [ :toarray "" ];
-:local Shown [ :toarray "" ];
-
-:foreach AccList in=[ / %PATH% access-list find where mac-address!="00:00:00:00:00:00" ] do={
- :local Mac [ / %PATH% access-list get $AccList mac-address ];
- :foreach SeenMac in=$Seen do={
- :if ($SeenMac = $Mac) do={
- :local Skip 0;
- :foreach ShownMac in=$Shown do={
- :if ($ShownMac = $Mac) do={ :set Skip 1; }
- }
- :if ($Skip = 0) do={
- / %PATH% access-list print where mac-address=$Mac;
- :set Shown ($Shown, $Mac);
-
- :put "\nNumeric id to remove, any key to skip!";
- :local Remove ([ :terminal inkey ] - 48);
- :if ($Remove >= 0 && $Remove <= 9) do={
- :put ("Removing numeric id " . $Remove . "...\n");
- / %PATH% access-list remove $Remove;
- }
- }
- }
- }
- :set Seen ($Seen, $Mac);
-}
diff --git a/accesslist-duplicates.template.rsc b/accesslist-duplicates.template.rsc
new file mode 100644
index 0000000..770fb30
--- /dev/null
+++ b/accesslist-duplicates.template.rsc
@@ -0,0 +1,43 @@
+#!rsc by RouterOS
+# RouterOS script: accesslist-duplicates%TEMPL%
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# print duplicate antries in wireless access list
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/accesslist-duplicates.md
+#
+# !! This is just a template to generate the real script!
+# !! Pattern '%TEMPL%' is replaced, paths are filtered.
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :local Seen ({});
+
+ :foreach AccList in=[ /caps-man/access-list/find where mac-address!="00:00:00:00:00:00" ] do={
+ :foreach AccList in=[ /interface/wifi/access-list/find where mac-address!="00:00:00:00:00:00" ] do={
+ :foreach AccList in=[ /interface/wireless/access-list/find where mac-address!="00:00:00:00:00:00" ] do={
+ :local Mac [ /caps-man/access-list/get $AccList mac-address ];
+ :local Mac [ /interface/wifi/access-list/get $AccList mac-address ];
+ :local Mac [ /interface/wireless/access-list/get $AccList mac-address ];
+ :if ($Seen->$Mac = 1) do={
+ /caps-man/access-list/print where mac-address=$Mac;
+ /interface/wifi/access-list/print where mac-address=$Mac;
+ /interface/wireless/access-list/print where mac-address=$Mac;
+ :local Remove [ :tonum [ /terminal/ask prompt="\nNumeric id to remove, any key to skip!" ] ];
+
+ :if ([ :typeof $Remove ] = "num") do={
+ :put ("Removing numeric id " . $Remove . "...\n");
+ /caps-man/access-list/remove $Remove;
+ /interface/wifi/access-list/remove $Remove;
+ /interface/wireless/access-list/remove $Remove;
+ }
+ }
+ :set ($Seen->$Mac) 1;
+ }
+} on-error={ }
diff --git a/accesslist-duplicates.wifi.rsc b/accesslist-duplicates.wifi.rsc
new file mode 100644
index 0000000..65f8aaa
--- /dev/null
+++ b/accesslist-duplicates.wifi.rsc
@@ -0,0 +1,34 @@
+#!rsc by RouterOS
+# RouterOS script: accesslist-duplicates.wifi
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+: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={ }
diff --git a/backup-cloud.rsc b/backup-cloud.rsc
new file mode 100644
index 0000000..cccb41b
--- /dev/null
+++ b/backup-cloud.rsc
@@ -0,0 +1,85 @@
+#!rsc by RouterOS
+# RouterOS script: backup-cloud
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: backup-script, order=40
+# requires RouterOS, version=7.12
+#
+# upload backup to MikroTik cloud
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/backup-cloud.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global BackupRandomDelay;
+ :global Identity;
+ :global PackagesUpdateBackupFailure;
+
+ :global DeviceInfo;
+ :global FormatLine;
+ :global HumanReadableNum;
+ :global LogPrint;
+ :global MkDir;
+ :global RandomDelay;
+ :global ScriptFromTerminal;
+ :global ScriptLock;
+ :global SendNotification2;
+ :global SymbolForNotification;
+ :global WaitForFile;
+ :global WaitFullyConnected;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :set PackagesUpdateBackupFailure 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!");
+ :error false;
+ }
+
+ :execute {
+ :global BackupPassword;
+ # we are not interested in output, but print is
+ # required to fetch information from cloud
+ /system/backup/cloud/print as-value;
+ :delay 20ms;
+ :if ([ :len [ /system/backup/cloud/find ] ] > 0) do={
+ /system/backup/cloud/upload-file action=create-and-upload \
+ password=$BackupPassword replace=[ get ([ find ]->0) name ];
+ } else={
+ /system/backup/cloud/upload-file action=create-and-upload \
+ password=$BackupPassword;
+ }
+ /file/add name="tmpfs/backup-cloud/done";
+ } as-string;
+
+ :if ([ $WaitForFile "tmpfs/backup-cloud/done" ] = true) do={
+ :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 ] . "iB") ] . "\n" . \
+ [ $FormatLine "Download key" ($Cloud->"secret-download-key") ]); silent=true });
+ } else={
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "floppy-disk,warning-sign" ] . "Cloud backup failed"); \
+ message=("Failed uploading backup for " . $Identity . " to cloud!\n\n" . [ $DeviceInfo ]) });
+ $LogPrint error $ScriptName ("Failed uploading backup for " . $Identity . " to cloud!");
+ :set PackagesUpdateBackupFailure true;
+ :error false;
+ }
+ /file/remove "tmpfs/backup-cloud";
+} on-error={ }
diff --git a/backup-email.rsc b/backup-email.rsc
new file mode 100644
index 0000000..64ca69c
--- /dev/null
+++ b/backup-email.rsc
@@ -0,0 +1,124 @@
+#!rsc by RouterOS
+# RouterOS script: backup-email
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: backup-script, order=20
+# requires RouterOS, version=7.12
+#
+# create and email backup and config file
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/backup-email.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global BackupPassword;
+ :global BackupRandomDelay;
+ :global BackupSendBinary;
+ :global BackupSendExport;
+ :global BackupSendGlobalConfig;
+ :global Domain;
+ :global Identity;
+ :global PackagesUpdateBackupFailure;
+
+ :global CleanName;
+ :global DeviceInfo;
+ :global FormatLine;
+ :global LogPrint;
+ :global MkDir;
+ :global RandomDelay;
+ :global ScriptFromTerminal;
+ :global ScriptLock;
+ :global SendEMail2;
+ :global SymbolForNotification;
+ :global WaitForFile;
+ :global WaitFullyConnected;
+
+ :if ([ :typeof $SendEMail2 ] = "nothing") do={
+ $LogPrint error $ScriptName ("The module for sending notifications via e-mail is not installed.");
+ :error false;
+ }
+
+ :if ($BackupSendBinary != true && \
+ $BackupSendExport != true) do={
+ $LogPrint error $ScriptName ("Configured to send neither backup nor config export.");
+ :error false;
+ }
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :set PackagesUpdateBackupFailure true;
+ :error false;
+ }
+ $WaitFullyConnected;
+
+ :if ([ $ScriptFromTerminal $ScriptName ] = false && $BackupRandomDelay > 0) do={
+ $RandomDelay $BackupRandomDelay;
+ }
+
+ # filename based on identity
+ :local DirName ("tmpfs/" . $ScriptName);
+ :local FileName [ $CleanName ($Identity . "." . $Domain) ];
+ :local FilePath ($DirName . "/" . $FileName);
+ :local BackupFile "none";
+ :local ExportFile "none";
+ :local ConfigFile "none";
+ :local Attach ({});
+
+ :if ([ $MkDir $DirName ] = false) do={
+ $LogPrint error $ScriptName ("Failed creating directory!");
+ :error false;
+ }
+
+ # binary backup
+ :if ($BackupSendBinary = true) do={
+ /system/backup/save encryption=aes-sha256 name=$FilePath password=$BackupPassword;
+ $WaitForFile ($FilePath . ".backup");
+ :set BackupFile ($FileName . ".backup");
+ :set Attach ($Attach, ($FilePath . ".backup"));
+ }
+
+ # create configuration export
+ :if ($BackupSendExport = true) do={
+ /export terse show-sensitive file=$FilePath;
+ $WaitForFile ($FilePath . ".rsc");
+ :set ExportFile ($FileName . ".rsc");
+ :set Attach ($Attach, ($FilePath . ".rsc"));
+ }
+
+ # global-config-overlay
+ :if ($BackupSendGlobalConfig = true) do={
+ # Do *NOT* use '/file/add ...' here, as it is limited to 4095 bytes!
+ :execute script={ :put [ /system/script/get global-config-overlay source ]; } \
+ file=($FilePath . ".conf\00");
+ $WaitForFile ($FilePath . ".conf");
+ :set ConfigFile ($FileName . ".conf");
+ :set Attach ($Attach, ($FilePath . ".conf"));
+ }
+
+ # send email with status and files
+ $SendEMail2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "floppy-disk,incoming-envelope" ] . \
+ "Backup & Config"); \
+ message=("See attached files for backup and config export for " . \
+ $Identity . ".\n\n" . \
+ [ $DeviceInfo ] . "\n\n" . \
+ [ $FormatLine "Backup file" $BackupFile ] . "\n" . \
+ [ $FormatLine "Export file" $ExportFile ] . "\n" . \
+ [ $FormatLine "Config file" $ConfigFile ]); \
+ attach=$Attach; remove-attach=true });
+
+ # wait for the mail to be sent
+ :local I 0;
+ :while ([ :len [ /file/find where name ~ ($FilePath . "\\.(backup|rsc)\$") ] ] > 0) do={
+ :if ($I >= 120) do={
+ $LogPrint warning $ScriptName ("Files are still available, sending e-mail failed.");
+ :set PackagesUpdateBackupFailure true;
+ :error false;
+ }
+ :delay 1s;
+ :set I ($I + 1);
+ }
+} on-error={ }
diff --git a/backup-partition.rsc b/backup-partition.rsc
new file mode 100644
index 0000000..503d382
--- /dev/null
+++ b/backup-partition.rsc
@@ -0,0 +1,57 @@
+#!rsc by RouterOS
+# RouterOS script: backup-partition
+# Copyright (c) 2022-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: backup-script, order=70
+# requires RouterOS, version=7.12
+#
+# save configuration to fallback partition
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/backup-partition.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global PackagesUpdateBackupFailure;
+
+ :global LogPrint;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :set PackagesUpdateBackupFailure true;
+ :error false;
+ }
+
+ :if ([ :len [ /partitions/find ] ] < 2) do={
+ $LogPrint error $ScriptName ("Device does not have a fallback partition.");
+ :set PackagesUpdateBackupFailure 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;
+ :error false;
+ }
+
+ :local FallbackTo [ /partitions/get $ActiveRunning fallback-to ];
+
+ :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 '" . $FallbackTo . "'.");
+ } on-error={
+ /system/scheduler/remove [ find where name="running-from-backup-partition" ];
+ $LogPrint error $ScriptName ("Failed saving configuration to partition '" . $FallbackTo . "'!");
+ :set PackagesUpdateBackupFailure true;
+ :error false;
+ }
+} on-error={ }
diff --git a/backup-upload.rsc b/backup-upload.rsc
new file mode 100644
index 0000000..ef5b7c7
--- /dev/null
+++ b/backup-upload.rsc
@@ -0,0 +1,161 @@
+#!rsc by RouterOS
+# RouterOS script: backup-upload
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: backup-script, order=50
+# requires RouterOS, version=7.12
+#
+# create and upload backup and config file
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/backup-upload.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+: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 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.");
+ :error false;
+ }
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :set PackagesUpdateBackupFailure true;
+ :error false;
+ }
+ $WaitFullyConnected;
+
+ :if ([ $ScriptFromTerminal $ScriptName ] = false && $BackupRandomDelay > 0) do={
+ $RandomDelay $BackupRandomDelay;
+ }
+
+ # filename based on identity
+ :local DirName ("tmpfs/" . $ScriptName);
+ :local FileName [ $CleanName ($Identity . "." . $Domain) ];
+ :local FilePath ($DirName . "/" . $FileName);
+ :local BackupFile "none";
+ :local ExportFile "none";
+ :local ConfigFile "none";
+ :local Failed 0;
+
+ :if ([ $MkDir $DirName ] = false) do={
+ $LogPrint error $ScriptName ("Failed creating directory!");
+ :error false;
+ }
+
+ # binary backup
+ :if ($BackupSendBinary = true) do={
+ /system/backup/save encryption=aes-sha256 name=$FilePath password=$BackupPassword;
+ $WaitForFile ($FilePath . ".backup");
+
+ :do {
+ /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".backup") \
+ user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".backup");
+ :set BackupFile [ /file/get ($FilePath . ".backup") ];
+ :set ($BackupFile->"name") ($FileName . ".backup");
+ } on-error={
+ $LogPrint error $ScriptName ("Uploading backup file failed!");
+ :set BackupFile "failed";
+ :set Failed 1;
+ }
+
+ /file/remove ($FilePath . ".backup");
+ }
+
+ # create configuration export
+ :if ($BackupSendExport = true) do={
+ /export terse show-sensitive file=$FilePath;
+ $WaitForFile ($FilePath . ".rsc");
+
+ :do {
+ /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".rsc") \
+ user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".rsc");
+ :set ExportFile [ /file/get ($FilePath . ".rsc") ];
+ :set ($ExportFile->"name") ($FileName . ".rsc");
+ } on-error={
+ $LogPrint error $ScriptName ("Uploading configuration export failed!");
+ :set ExportFile "failed";
+ :set Failed 1;
+ }
+
+ /file/remove ($FilePath . ".rsc");
+ }
+
+ # global-config-overlay
+ :if ($BackupSendGlobalConfig = true) do={
+ # Do *NOT* use '/file/add ...' here, as it is limited to 4095 bytes!
+ :execute script={ :put [ /system/script/get global-config-overlay source ]; } \
+ file=($FilePath . ".conf\00");
+ $WaitForFile ($FilePath . ".conf");
+
+ :do {
+ /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".conf") \
+ user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".conf");
+ :set ConfigFile [ /file/get ($FilePath . ".conf") ];
+ :set ($ConfigFile->"name") ($FileName . ".conf");
+ } on-error={
+ $LogPrint error $ScriptName ("Uploading global-config-overlay failed!");
+ :set ConfigFile "failed";
+ :set Failed 1;
+ }
+
+ /file/remove ($FilePath . ".conf");
+ }
+
+ :local FileInfo do={
+ :local Name $1;
+ :local File $2;
+
+ :global FormatLine;
+ :global HumanReadableNum;
+ :global IfThenElse;
+
+ :return \
+ [ $IfThenElse ([ :typeof $File ] = "array") \
+ ($Name . ":\n" . [ $FormatLine " name" ($File->"name") ] . "\n" . \
+ [ $FormatLine " size" ([ $HumanReadableNum ($File->"size") 1024 ] . "iB") ]) \
+ [ $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;
+ :error false;
+ }
+} on-error={ }
diff --git a/bridge-port-to-default b/bridge-port-to-default
deleted file mode 100644
index b8503df..0000000
--- a/bridge-port-to-default
+++ /dev/null
@@ -1,53 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: bridge-port-to-default
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# reset bridge ports to default bridge
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/bridge-port.md
-
-:local 0 "bridge-port-to-default";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global BridgePortTo;
-
-:global IfThenElse;
-:global LogPrintExit2;
-:global ParseKeyValueStore;
-
-:foreach BridgePort in=[ / interface bridge port find where !(comment=[]) ] do={
- :local BridgePortVal [ / interface bridge port get $BridgePort ];
- :foreach Config,BridgeDefault in=[ $ParseKeyValueStore ($BridgePortVal->"comment") ] do={
- :if ($Config = $BridgePortTo) do={
- :local DHCPClient [ / ip dhcp-client find where interface=$BridgePortVal->"interface" comment="toggle with bridge port" ];
-
- :if ($BridgeDefault = "dhcp-client") do={
- :if ([ :len $DHCPClient ] != 1) do={
- $LogPrintExit2 warning $0 ([ $IfThenElse ([ :len $DHCPClient ] = 0) "Missing" "Duplicate" ] . \
- " dhcp client configuration for interface " . $BridgePortVal->"interface" . "!") true;
- }
- :local DHCPClientDisabled [ / ip dhcp-client get $DHCPClient disabled ];
-
- :if ($BridgePortVal->"disabled" = false || $DHCPClientDisabled = true) do={
- $LogPrintExit2 info $0 ("Disabling bridge port for interface " . $BridgePortVal->"interface" . ", enabling dhcp client.") false;
- / interface bridge port disable $BridgePort;
- / ip dhcp-client enable $DHCPClient;
- }
- } else={
- :if ($BridgePortVal->"disabled" = true || $BridgeDefault != $BridgePortVal->"bridge") do={
- $LogPrintExit2 info $0 ("Enabling bridge port for interface " . $BridgePortVal->"interface" . ", changing to " . $BridgePortTo . \
- " bridge " . $BridgeDefault . ", disabling dhcp client.") false;
- :if ([ :len $DHCPClient ] = 1) do={
- / ip dhcp-client disable $DHCPClient;
- :delay 200ms;
- }
- / interface bridge port set disabled=no bridge=$BridgeDefault $BridgePort;
- } else={
- $LogPrintExit2 debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $BridgePortTo . \
- " bridge " . $BridgeDefault . ".") false;
- }
- }
- }
- }
-}
diff --git a/bridge-port-toggle b/bridge-port-toggle
deleted file mode 100644
index 9eeab35..0000000
--- a/bridge-port-toggle
+++ /dev/null
@@ -1,21 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: bridge-port-toggle
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# toggle bridge ports between default and alt bridge
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/bridge-port.md
-
-:local 0 "bridge-port-toggle";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global BridgePortTo;
-
-:if ($BridgePortTo != "default") do={
- :set BridgePortTo "default";
-} else={
- :set BridgePortTo "alt";
-}
-
-/ system script run bridge-port-to-default;
diff --git a/capsman-download-packages b/capsman-download-packages
deleted file mode 100644
index 5b03bba..0000000
--- a/capsman-download-packages
+++ /dev/null
@@ -1,62 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: capsman-download-packages
-# Copyright (c) 2018-2021 Christian Hesse <mail@eworm.de>
-# Michael Gisbers <michael@gisbers.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# download and cleanup packages for CAP installation from CAPsMAN
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/capsman-download-packages.md
-
-:local 0 "capsman-download-packages";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global CleanFilePath;
-:global DownloadPackage;
-:global LogPrintExit2;
-:global MkDir;
-:global ScriptLock;
-:global WaitFullyConnected;
-
-$ScriptLock $0;
-$WaitFullyConnected;
-
-:local PackagePath [ $CleanFilePath [ / caps-man manager get package-path ] ];
-:local InstalledVersion [ / system package update get installed-version ];
-:local Updated false;
-
-:if ([ :len $PackagePath ] = 0) do={
- $LogPrintExit2 warning $0 ("The CAPsMAN package path is not defined, can not download packages.") true;
-}
-
-:if ([ :len [ / file find where name=$PackagePath type="directory" ] ] = 0) do={
- :if ([ $MkDir $PackagePath ] = false) do={
- $LogPrintExit2 warning $0 ("Creating directory at CAPsMAN package path (" . \
- $PackagePath . ") failed!") true;
- }
- $LogPrintExit2 info $0 ("Created directory at CAPsMAN package path (" . $PackagePath . \
- "). Please place your packages!") false;
-}
-
-:foreach Package in=[ / file find where type=package \
- package-version!=$InstalledVersion name~("^" . $PackagePath) ] do={
- :local File [ / file get $Package ];
- :if ($File->"package-architecture" = "mips") do={
- :set ($File->"package-architecture") "mipsbe";
- }
- :if ($File->"package-name" = "wireless@") do={
- :set ($File->"package-name") "wireless";
- }
- :if ([ $DownloadPackage ($File->"package-name") $InstalledVersion ($File->"package-architecture") $PackagePath ] = true) do={
- :set Updated true;
- / file remove $Package;
- }
-}
-
-:if ($Updated = true) do={
- :if ([ :len [ / system script find where name="capsman-rolling-upgrade" ] ] > 0) do={
- / system script run capsman-rolling-upgrade;
- } else={
- / caps-man remote-cap upgrade [ find where version!=$InstalledVersion ];
- }
-}
diff --git a/capsman-download-packages.capsman.rsc b/capsman-download-packages.capsman.rsc
new file mode 100644
index 0000000..a3bd4a5
--- /dev/null
+++ b/capsman-download-packages.capsman.rsc
@@ -0,0 +1,83 @@
+#!rsc by RouterOS
+# RouterOS script: capsman-download-packages.capsman
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# Michael Gisbers <michael@gisbers.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global CleanFilePath;
+ :global DownloadPackage;
+ :global LogPrint;
+ :global MkDir;
+ :global ScriptLock;
+ :global WaitFullyConnected;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+ $WaitFullyConnected;
+
+ :local PackagePath [ $CleanFilePath [ /caps-man/manager/get package-path ] ];
+ :local InstalledVersion [ /system/package/update/get installed-version ];
+ :local Updated false;
+
+ :if ([ :len $PackagePath ] = 0) do={
+ $LogPrint warning $ScriptName ("The CAPsMAN package path is not defined, can not download packages.");
+ :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!");
+ :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;
+ /file/remove $Package;
+ }
+ }
+
+ :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 ];
+ }
+ }
+} on-error={ }
diff --git a/capsman-download-packages.template.rsc b/capsman-download-packages.template.rsc
new file mode 100644
index 0000000..cad3bcb
--- /dev/null
+++ b/capsman-download-packages.template.rsc
@@ -0,0 +1,94 @@
+#!rsc by RouterOS
+# RouterOS script: capsman-download-packages%TEMPL%
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# Michael Gisbers <michael@gisbers.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# download and cleanup packages for CAP installation from CAPsMAN
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/capsman-download-packages.md
+#
+# !! This is just a template to generate the real script!
+# !! Pattern '%TEMPL%' is replaced, paths are filtered.
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global CleanFilePath;
+ :global DownloadPackage;
+ :global LogPrint;
+ :global MkDir;
+ :global ScriptLock;
+ :global WaitFullyConnected;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+ $WaitFullyConnected;
+
+ :local PackagePath [ $CleanFilePath [ /caps-man/manager/get package-path ] ];
+ :local PackagePath [ $CleanFilePath [ /interface/wifi/capsman/get package-path ] ];
+ :local InstalledVersion [ /system/package/update/get installed-version ];
+ :local Updated false;
+
+ :if ([ :len $PackagePath ] = 0) do={
+ $LogPrint warning $ScriptName ("The CAPsMAN package path is not defined, can not download packages.");
+ :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!");
+ :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;
+ /file/remove $Package;
+ }
+ }
+
+ :if ([ :len [ /file/find where type=package name~("^" . $PackagePath) ] ] = 0) do={
+ $LogPrint info $ScriptName ("No packages available, downloading default set.");
+# NOT /interface/wifi/ #
+ :foreach Arch in={ "arm"; "mipsbe" } do={
+ :foreach Package in={ "routeros"; "wireless" } do={
+# NOT /interface/wifi/ #
+# NOT /caps-man/ #
+ :foreach Arch in={ "arm"; "arm64" } do={
+ :local Packages { "arm"={ "routeros"; "wifi-qcom"; "wifi-qcom-ac" };
+ "arm64"={ "routeros"; "wifi-qcom" } };
+ :foreach Package in=($Packages->$Arch) do={
+# NOT /caps-man/ #
+ :if ([ $DownloadPackage $Package $InstalledVersion $Arch $PackagePath ] = true) do={
+ :set Updated true;
+ }
+ }
+ }
+ }
+
+ :if ($Updated = true) do={
+ :local 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/wifi/capsman/remote-cap/upgrade [ find where version!=$InstalledVersion ];
+ }
+ }
+} on-error={ }
diff --git a/capsman-download-packages.wifi.rsc b/capsman-download-packages.wifi.rsc
new file mode 100644
index 0000000..909688f
--- /dev/null
+++ b/capsman-download-packages.wifi.rsc
@@ -0,0 +1,85 @@
+#!rsc by RouterOS
+# RouterOS script: capsman-download-packages.wifi
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# Michael Gisbers <michael@gisbers.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global CleanFilePath;
+ :global DownloadPackage;
+ :global LogPrint;
+ :global MkDir;
+ :global ScriptLock;
+ :global WaitFullyConnected;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :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.");
+ :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!");
+ :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;
+ /file/remove $Package;
+ }
+ }
+
+ :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 Script ([ /system/script/find where source~"\n# provides: capsman-rolling-upgrade\n" ]->0);
+ :if ([ :len $Script ] > 0) do={
+ /system/script/run $Script;
+ } else={
+ /interface/wifi/capsman/remote-cap/upgrade [ find where version!=$InstalledVersion ];
+ }
+ }
+} on-error={ }
diff --git a/capsman-rolling-upgrade b/capsman-rolling-upgrade
deleted file mode 100644
index 9e7aec0..0000000
--- a/capsman-rolling-upgrade
+++ /dev/null
@@ -1,32 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: capsman-rolling-upgrade
-# Copyright (c) 2018-2021 Christian Hesse <mail@eworm.de>
-# Michael Gisbers <michael@gisbers.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# upgrade CAPs one after another
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/capsman-rolling-upgrade.md
-
-:local 0 "capsman-rolling-upgrade";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global LogPrintExit2;
-:global ScriptLock;
-
-$ScriptLock $0;
-
-:local InstalledVersion [ / system package update get installed-version ];
-
-:local RemoteCapCount [ :len [ / caps-man remote-cap find ] ];
-:if ($RemoteCapCount > 0) do={
- :local Delay (600 / $RemoteCapCount);
- :if ($Delay > 120) do={ :set Delay 120; }
- :foreach RemoteCap in=[ / caps-man remote-cap find where version!=$InstalledVersion ] do={
- :local RemoteCapVal [ / caps-man remote-cap get $RemoteCap ];
- $LogPrintExit2 info $0 ("Starting upgrade for " . $RemoteCapVal->"name" . \
- " (" . $RemoteCapVal->"identity" . ")...") false;
- / caps-man remote-cap upgrade [ find where name=$RemoteCapVal->"name" ];
- :delay ($Delay . "s");
- }
-}
diff --git a/capsman-rolling-upgrade.capsman.rsc b/capsman-rolling-upgrade.capsman.rsc
new file mode 100644
index 0000000..11bfd69
--- /dev/null
+++ b/capsman-rolling-upgrade.capsman.rsc
@@ -0,0 +1,46 @@
+#!rsc by RouterOS
+# RouterOS script: capsman-rolling-upgrade.capsman
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# Michael Gisbers <michael@gisbers.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: capsman-rolling-upgrade
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global LogPrint;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :local InstalledVersion [ /system/package/update/get installed-version ];
+
+ :local RemoteCapCount [ :len [ /caps-man/remote-cap/find ] ];
+ :if ($RemoteCapCount > 0) do={
+ :local Delay (600 / $RemoteCapCount);
+ :if ($Delay > 120) do={ :set Delay 120; }
+ :foreach RemoteCap in=[ /caps-man/remote-cap/find where version!=$InstalledVersion ] do={
+ :local RemoteCapVal [ /caps-man/remote-cap/get $RemoteCap ];
+ :if ([ :len $RemoteCapVal ] > 1) do={
+ $LogPrint info $ScriptName ("Starting upgrade for " . $RemoteCapVal->"name" . \
+ " (" . $RemoteCapVal->"identity" . ")...");
+ /caps-man/remote-cap/upgrade $RemoteCap;
+ } else={
+ $LogPrint warning $ScriptName ("Remote CAP vanished, skipping upgrade.");
+ }
+ :delay ($Delay . "s");
+ }
+ }
+} on-error={ }
diff --git a/capsman-rolling-upgrade.template.rsc b/capsman-rolling-upgrade.template.rsc
new file mode 100644
index 0000000..e0effd4
--- /dev/null
+++ b/capsman-rolling-upgrade.template.rsc
@@ -0,0 +1,54 @@
+#!rsc by RouterOS
+# RouterOS script: capsman-rolling-upgrade%TEMPL%
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# Michael Gisbers <michael@gisbers.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: capsman-rolling-upgrade
+# requires RouterOS, version=7.12
+#
+# upgrade CAPs one after another
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/capsman-rolling-upgrade.md
+#
+# !! This is just a template to generate the real script!
+# !! Pattern '%TEMPL%' is replaced, paths are filtered.
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global LogPrint;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :local InstalledVersion [ /system/package/update/get installed-version ];
+
+ :local RemoteCapCount [ :len [ /caps-man/remote-cap/find ] ];
+ :local RemoteCapCount [ :len [ /interface/wifi/capsman/remote-cap/find ] ];
+ :if ($RemoteCapCount > 0) do={
+ :local Delay (600 / $RemoteCapCount);
+ :if ($Delay > 120) do={ :set Delay 120; }
+ :foreach RemoteCap in=[ /caps-man/remote-cap/find where version!=$InstalledVersion ] do={
+ :foreach RemoteCap in=[ /interface/wifi/capsman/remote-cap/find where version!=$InstalledVersion ] do={
+ :local RemoteCapVal [ /caps-man/remote-cap/get $RemoteCap ];
+ :local RemoteCapVal [ /interface/wifi/capsman/remote-cap/get $RemoteCap ];
+ :if ([ :len $RemoteCapVal ] > 1) do={
+# NOT /caps-man/ #
+ :set ($RemoteCapVal->"name") ($RemoteCapVal->"common-name");
+# NOT /caps-man/ #
+ $LogPrint info $ScriptName ("Starting upgrade for " . $RemoteCapVal->"name" . \
+ " (" . $RemoteCapVal->"identity" . ")...");
+ /caps-man/remote-cap/upgrade $RemoteCap;
+ /interface/wifi/capsman/remote-cap/upgrade $RemoteCap;
+ } else={
+ $LogPrint warning $ScriptName ("Remote CAP vanished, skipping upgrade.");
+ }
+ :delay ($Delay . "s");
+ }
+ }
+} on-error={ }
diff --git a/capsman-rolling-upgrade.wifi.rsc b/capsman-rolling-upgrade.wifi.rsc
new file mode 100644
index 0000000..8ec6f26
--- /dev/null
+++ b/capsman-rolling-upgrade.wifi.rsc
@@ -0,0 +1,47 @@
+#!rsc by RouterOS
+# RouterOS script: capsman-rolling-upgrade.wifi
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# Michael Gisbers <michael@gisbers.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: capsman-rolling-upgrade
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global LogPrint;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :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={ }
diff --git a/certificate-renew-issued b/certificate-renew-issued
deleted file mode 100644
index c8ce4ae..0000000
--- a/certificate-renew-issued
+++ /dev/null
@@ -1,38 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: certificate-renew-issued
-# Copyright (c) 2019-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# renew locally issued certificates
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/certificate-renew-issued.md
-
-:local 0 "certificate-renew-issued";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global CertIssuedExportPass;
-
-:global LogPrintExit2;
-:global MkDir;
-
-:foreach Cert in=[ / certificate find where issued expires-after<3w ] do={
- :local CertVal [ / certificate get $Cert ];
- / certificate issued-revoke $Cert;
- / certificate set name=($CertVal->"name" . "-revoked-" . [ / system clock get date ]) $Cert;
- / certificate add name=($CertVal->"name") common-name=($CertVal->"common-name") \
- key-usage=($CertVal->"key-usage") subject-alt-name=($CertVal->"subject-alt-name");
- / certificate sign ($CertVal->"name") ca=($CertVal->"ca");
- :if ([ :typeof ($CertIssuedExportPass->($CertVal->"common-name")) ] = "str") do={
- :if ([ $MkDir "cert-issued" ] = true) do={
- / certificate export-certificate ($CertVal->"name") type=pkcs12 \
- file-name=("cert-issued/" . $CertVal->"common-name") \
- export-passphrase=($CertIssuedExportPass->($CertVal->"common-name"));
- $LogPrintExit2 info $0 ("Issued a new certificate for \"" . $CertVal->"common-name" . \
- "\", exported to \"cert-issued/" . $CertVal->"common-name" . ".p12\".") false;
- } else={
- $LogPrintExit2 warning $0 ("Failed creating directory, not exporting certificate.") false;
- }
- } else={
- $LogPrintExit2 info $0 ("Issued a new certificate for \"" . $CertVal->"common-name" . "\".") false;
- }
-}
diff --git a/certificate-renew-issued.rsc b/certificate-renew-issued.rsc
new file mode 100644
index 0000000..45805c5
--- /dev/null
+++ b/certificate-renew-issued.rsc
@@ -0,0 +1,48 @@
+#!rsc by RouterOS
+# RouterOS script: certificate-renew-issued
+# Copyright (c) 2019-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# renew locally issued certificates
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/certificate-renew-issued.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global CertIssuedExportPass;
+
+ :global LogPrint;
+ :global MkDir;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :foreach Cert in=[ /certificate/find where issued expires-after<3w ] do={
+ :local CertVal [ /certificate/get $Cert ];
+ /certificate/issued-revoke $Cert;
+ /certificate/set name=($CertVal->"name" . "-revoked-" . [ /system/clock/get date ]) $Cert;
+ /certificate/add name=($CertVal->"name") common-name=($CertVal->"common-name") \
+ key-usage=($CertVal->"key-usage") subject-alt-name=($CertVal->"subject-alt-name");
+ /certificate/sign ($CertVal->"name") ca=($CertVal->"ca");
+ :if ([ :typeof ($CertIssuedExportPass->($CertVal->"common-name")) ] = "str") do={
+ :if ([ $MkDir "cert-issued" ] = true) do={
+ /certificate/export-certificate ($CertVal->"name") type=pkcs12 \
+ file-name=("cert-issued/" . $CertVal->"common-name") \
+ export-passphrase=($CertIssuedExportPass->($CertVal->"common-name"));
+ $LogPrint info $ScriptName ("Issued a new certificate for \"" . $CertVal->"common-name" . \
+ "\", exported to \"cert-issued/" . $CertVal->"common-name" . ".p12\".");
+ } else={
+ $LogPrint warning $ScriptName ("Failed creating directory, not exporting certificate.");
+ }
+ } else={
+ $LogPrint info $ScriptName ("Issued a new certificate for \"" . $CertVal->"common-name" . "\".");
+ }
+ }
+} on-error={ }
diff --git a/certs/Cloudflare Inc ECC CA-3.pem b/certs/Cloudflare-Inc-ECC-CA-3.pem
index e2de16a..fa91603 100644
--- a/certs/Cloudflare Inc ECC CA-3.pem
+++ b/certs/Cloudflare-Inc-ECC-CA-3.pem
@@ -24,8 +24,7 @@ Certificate:
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:
- keyid:E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
-
+ 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:
@@ -34,12 +33,9 @@ Certificate:
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
@@ -47,23 +43,23 @@ Certificate:
Policy: 2.23.140.1.2.1
Policy: 2.23.140.1.2.2
Policy: 2.23.140.1.2.3
-
Signature Algorithm: sha256WithRSAEncryption
- 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
+ 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
@@ -99,7 +95,7 @@ Certificate:
Subject: C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
- RSA Public-Key: (2048 bit)
+ 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:
@@ -128,21 +124,22 @@ Certificate:
X509v3 Key Usage: critical
Certificate Sign, CRL Sign
Signature Algorithm: sha1WithRSAEncryption
- 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
+ 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
diff --git a/certs/DigiCert-Global-G2-TLS-RSA-SHA256-2020-CA1.pem b/certs/DigiCert-Global-G2-TLS-RSA-SHA256-2020-CA1.pem
new file mode 100644
index 0000000..12084ee
--- /dev/null
+++ b/certs/DigiCert-Global-G2-TLS-RSA-SHA256-2020-CA1.pem
@@ -0,0 +1,182 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 0c:f5:bd:06:2b:56:02:f4:7a:b8:50:2c:23:cc:f0:66
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2
+ Validity
+ Not Before: Mar 30 00:00:00 2021 GMT
+ Not After : Mar 29 23:59:59 2031 GMT
+ Subject: C=US, O=DigiCert Inc, CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:cc:f7:10:62:4f:a6:bb:63:6f:ed:90:52:56:c5:
+ 6d:27:7b:7a:12:56:8a:f1:f4:f9:d6:e7:e1:8f:bd:
+ 95:ab:f2:60:41:15:70:db:12:00:fa:27:0a:b5:57:
+ 38:5b:7d:b2:51:93:71:95:0e:6a:41:94:5b:35:1b:
+ fa:7b:fa:bb:c5:be:24:30:fe:56:ef:c4:f3:7d:97:
+ e3:14:f5:14:4d:cb:a7:10:f2:16:ea:ab:22:f0:31:
+ 22:11:61:69:90:26:ba:78:d9:97:1f:e3:7d:66:ab:
+ 75:44:95:73:c8:ac:ff:ef:5d:0a:8a:59:43:e1:ac:
+ b2:3a:0f:f3:48:fc:d7:6b:37:c1:63:dc:de:46:d6:
+ db:45:fe:7d:23:fd:90:e8:51:07:1e:51:a3:5f:ed:
+ 49:46:54:7f:2c:88:c5:f4:13:9c:97:15:3c:03:e8:
+ a1:39:dc:69:0c:32:c1:af:16:57:4c:94:47:42:7c:
+ a2:c8:9c:7d:e6:d4:4d:54:af:42:99:a8:c1:04:c2:
+ 77:9c:d6:48:e4:ce:11:e0:2a:80:99:f0:43:70:cf:
+ 3f:76:6b:d1:4c:49:ab:24:5e:c2:0d:82:fd:46:a8:
+ ab:6c:93:cc:62:52:42:75:92:f8:9a:fa:5e:5e:b2:
+ b0:61:e5:1f:1f:b9:7f:09:98:e8:3d:fa:83:7f:47:
+ 69:a1
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Subject Key Identifier:
+ 74:85:80:C0:66:C7:DF:37:DE:CF:BD:29:37:AA:03:1D:BE:ED:CD:17
+ X509v3 Authority Key Identifier:
+ 4E:22:54:20:18:95:E6:E3:6E:E6:0F:FA:FA:B9:12:ED:06:17:8F:39
+ 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/DigiCertGlobalRootG2.crt
+ X509v3 CRL Distribution Points:
+ Full Name:
+ URI:http://crl3.digicert.com/DigiCertGlobalRootG2.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: sha256WithRSAEncryption
+ Signature Value:
+ 90:f1:70:cb:28:97:69:97:7c:74:fd:c0:fa:26:7b:53:ab:ad:
+ cd:65:fd:ba:9c:06:9c:8a:d7:5a:43:87:ed:4d:4c:56:5f:ad:
+ c1:c5:b5:05:20:2e:59:d1:ff:4a:f5:a0:2a:d8:b0:95:ad:c9:
+ 2e:4a:3b:d7:a7:f6:6f:88:29:fc:30:3f:24:84:bb:c3:b7:7b:
+ 93:07:2c:af:87:6b:76:33:ed:00:55:52:b2:59:9e:e4:b9:d0:
+ f3:df:e7:0f:fe:dd:f8:c4:b9:10:72:81:09:04:5f:cf:97:9e:
+ 2e:32:75:8e:cf:9a:58:d2:57:31:7e:37:01:81:b2:66:6d:29:
+ 1a:b1:66:09:6d:d1:6e:90:f4:b9:fa:2f:01:14:c5:5c:56:64:
+ 01:d9:7d:87:a8:38:53:9f:8b:5d:46:6d:5c:c6:27:84:81:d4:
+ 7e:8c:8c:a3:9b:52:e7:c6:88:ec:37:7c:2a:fb:f0:55:5a:38:
+ 72:10:d8:00:13:cf:4c:73:db:aa:37:35:a8:29:81:69:9c:76:
+ bc:de:18:7b:90:d4:ca:cf:ef:67:03:fd:04:5a:21:16:b1:ff:
+ ea:3f:df:dc:82:f5:eb:f4:59:92:23:0d:24:2a:95:25:4c:ca:
+ a1:91:e6:d4:b7:ac:87:74:b3:f1:6d:a3:99:db:f9:d5:bd:84:
+ 40:9f:07:98
+-----BEGIN CERTIFICATE-----
+MIIEyDCCA7CgAwIBAgIQDPW9BitWAvR6uFAsI8zwZjANBgkqhkiG9w0BAQsFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
+MjAeFw0yMTAzMzAwMDAwMDBaFw0zMTAzMjkyMzU5NTlaMFkxCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2Jh
+bCBHMiBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMz3EGJPprtjb+2QUlbFbSd7ehJWivH0+dbn4Y+9lavyYEEV
+cNsSAPonCrVXOFt9slGTcZUOakGUWzUb+nv6u8W+JDD+Vu/E832X4xT1FE3LpxDy
+FuqrIvAxIhFhaZAmunjZlx/jfWardUSVc8is/+9dCopZQ+GssjoP80j812s3wWPc
+3kbW20X+fSP9kOhRBx5Ro1/tSUZUfyyIxfQTnJcVPAPooTncaQwywa8WV0yUR0J8
+osicfebUTVSvQpmowQTCd5zWSOTOEeAqgJnwQ3DPP3Zr0UxJqyRewg2C/Uaoq2yT
+zGJSQnWS+Jr6Xl6ysGHlHx+5fwmY6D36g39HaaECAwEAAaOCAYIwggF+MBIGA1Ud
+EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHSFgMBmx9833s+9KTeqAx2+7c0XMB8G
+A1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA4GA1UdDwEB/wQEAwIBhjAd
+BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYIKwYBBQUHAQEEajBoMCQG
+CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQAYIKwYBBQUHMAKG
+NGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RH
+Mi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29t
+L0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDA9BgNVHSAENjA0MAsGCWCGSAGG/WwC
+ATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgGBmeBDAECAzANBgkqhkiG
+9w0BAQsFAAOCAQEAkPFwyyiXaZd8dP3A+iZ7U6utzWX9upwGnIrXWkOH7U1MVl+t
+wcW1BSAuWdH/SvWgKtiwla3JLko716f2b4gp/DA/JIS7w7d7kwcsr4drdjPtAFVS
+slme5LnQ89/nD/7d+MS5EHKBCQRfz5eeLjJ1js+aWNJXMX43AYGyZm0pGrFmCW3R
+bpD0ufovARTFXFZkAdl9h6g4U5+LXUZtXMYnhIHUfoyMo5tS58aI7Dd8KvvwVVo4
+chDYABPPTHPbqjc1qCmBaZx2vN4Ye5DUys/vZwP9BFohFrH/6j/f3IL16/RZkiMN
+JCqVJUzKoZHm1Lesh3Sz8W2jmdv51b2EQJ8HmA==
+-----END CERTIFICATE-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 03:3a:f1:e6:a7:11:a9:a0:bb:28:64:b1:1d:09:fa:e5
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2
+ Validity
+ Not Before: Aug 1 12:00:00 2013 GMT
+ Not After : Jan 15 12:00:00 2038 GMT
+ Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:bb:37:cd:34:dc:7b:6b:c9:b2:68:90:ad:4a:75:
+ ff:46:ba:21:0a:08:8d:f5:19:54:c9:fb:88:db:f3:
+ ae:f2:3a:89:91:3c:7a:e6:ab:06:1a:6b:cf:ac:2d:
+ e8:5e:09:24:44:ba:62:9a:7e:d6:a3:a8:7e:e0:54:
+ 75:20:05:ac:50:b7:9c:63:1a:6c:30:dc:da:1f:19:
+ b1:d7:1e:de:fd:d7:e0:cb:94:83:37:ae:ec:1f:43:
+ 4e:dd:7b:2c:d2:bd:2e:a5:2f:e4:a9:b8:ad:3a:d4:
+ 99:a4:b6:25:e9:9b:6b:00:60:92:60:ff:4f:21:49:
+ 18:f7:67:90:ab:61:06:9c:8f:f2:ba:e9:b4:e9:92:
+ 32:6b:b5:f3:57:e8:5d:1b:cd:8c:1d:ab:95:04:95:
+ 49:f3:35:2d:96:e3:49:6d:dd:77:e3:fb:49:4b:b4:
+ ac:55:07:a9:8f:95:b3:b4:23:bb:4c:6d:45:f0:f6:
+ a9:b2:95:30:b4:fd:4c:55:8c:27:4a:57:14:7c:82:
+ 9d:cd:73:92:d3:16:4a:06:0c:8c:50:d1:8f:1e:09:
+ be:17:a1:e6:21:ca:fd:83:e5:10:bc:83:a5:0a:c4:
+ 67:28:f6:73:14:14:3d:46:76:c3:87:14:89:21:34:
+ 4d:af:0f:45:0c:a6:49:a1:ba:bb:9c:c5:b1:33:83:
+ 29:85
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ X509v3 Subject Key Identifier:
+ 4E:22:54:20:18:95:E6:E3:6E:E6:0F:FA:FA:B9:12:ED:06:17:8F:39
+ Signature Algorithm: sha256WithRSAEncryption
+ Signature Value:
+ 60:67:28:94:6f:0e:48:63:eb:31:dd:ea:67:18:d5:89:7d:3c:
+ c5:8b:4a:7f:e9:be:db:2b:17:df:b0:5f:73:77:2a:32:13:39:
+ 81:67:42:84:23:f2:45:67:35:ec:88:bf:f8:8f:b0:61:0c:34:
+ a4:ae:20:4c:84:c6:db:f8:35:e1:76:d9:df:a6:42:bb:c7:44:
+ 08:86:7f:36:74:24:5a:da:6c:0d:14:59:35:bd:f2:49:dd:b6:
+ 1f:c9:b3:0d:47:2a:3d:99:2f:bb:5c:bb:b5:d4:20:e1:99:5f:
+ 53:46:15:db:68:9b:f0:f3:30:d5:3e:31:e2:8d:84:9e:e3:8a:
+ da:da:96:3e:35:13:a5:5f:f0:f9:70:50:70:47:41:11:57:19:
+ 4e:c0:8f:ae:06:c4:95:13:17:2f:1b:25:9f:75:f2:b1:8e:99:
+ a1:6f:13:b1:41:71:fe:88:2a:c8:4f:10:20:55:d7:f3:14:45:
+ e5:e0:44:f4:ea:87:95:32:93:0e:fe:53:46:fa:2c:9d:ff:8b:
+ 22:b9:4b:d9:09:45:a4:de:a4:b8:9a:58:dd:1b:7d:52:9f:8e:
+ 59:43:88:81:a4:9e:26:d5:6f:ad:dd:0d:c6:37:7d:ed:03:92:
+ 1b:e5:77:5f:76:ee:3c:8d:c4:5d:56:5b:a2:d9:66:6e:b3:35:
+ 37:e5:32:b6
+-----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 ECC Secure Server CA.pem b/certs/DigiCert-TLS-Hybrid-ECC-SHA384-2020-CA1.pem
index 3eca7fc..446f56f 100644
--- a/certs/DigiCert ECC Secure Server CA.pem
+++ b/certs/DigiCert-TLS-Hybrid-ECC-SHA384-2020-CA1.pem
@@ -2,33 +2,41 @@ Certificate:
Data:
Version: 3 (0x2)
Serial Number:
- 0a:cb:28:ba:46:5e:e5:39:08:76:74:70:f3:cd:c6:12
+ 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: Mar 8 12:00:00 2013 GMT
- Not After : Mar 8 12:00:00 2023 GMT
- Subject: C = US, O = DigiCert Inc, CN = DigiCert ECC Secure Server CA
+ 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:e2:08:42:ea:77:d8:24:de:a0:2c:64:a4:13:ce:
- 40:9c:23:72:a9:02:0a:0e:37:3f:21:36:b8:8d:53:
- 14:f6:d5:91:95:4b:f3:96:02:8d:71:1e:c4:d8:cb:
- a7:9f:5e:ef:a0:e6:7f:5a:92:11:96:53:6f:eb:c0:
- cb:3f:ae:fd:5b:3f:47:24:e7:9a:07:2e:96:be:a8:
- 2f:bb:57:18:af:71:a4:bd:78:3a:1e:e8:5b:3c:6b:
- 64:11:2b:cc:34:2b:8c
+ 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:
@@ -36,51 +44,51 @@ Certificate:
URI:http://crl3.digicert.com/DigiCertGlobalRootCA.crl
X509v3 Certificate Policies:
- Policy: X509v3 Any Policy
- CPS: https://www.digicert.com/CPS
-
- X509v3 Subject Key Identifier:
- A3:9D:E6:1F:F9:DA:39:4F:C0:6E:E8:91:CB:95:A5:DA:31:E2:0A:9F
- 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
+ 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
- c7:8a:a0:43:4b:ec:74:c9:c5:ab:d5:1f:30:35:36:6e:98:56:
- 7b:48:ac:05:63:ae:7b:9a:57:24:57:cc:6f:fa:de:ab:6d:9c:
- c7:b6:ba:ec:ce:e7:ca:73:64:db:df:04:37:0a:00:49:b3:3f:
- 9f:26:ad:91:8c:20:e8:1f:88:0e:2a:fb:66:37:c8:30:e8:d2:
- c2:24:a7:45:48:2d:ea:01:50:4a:31:94:13:df:8d:5f:da:2a:
- bb:49:3c:61:f3:79:c8:9c:66:92:1a:96:2a:f4:7b:36:58:a3:
- 2c:41:10:74:1a:d3:ed:48:b6:d2:bb:8a:06:45:71:33:10:30:
- 7a:7a:98:21:dd:24:b9:ec:9c:b5:92:07:ad:83:c6:c4:6a:f8:
- 77:e6:35:be:13:0f:27:64:b2:43:bf:83:e9:77:56:db:08:87:
- 94:47:14:f5:5f:28:af:a3:68:4c:83:8f:60:f7:96:80:79:85:
- 6a:76:26:9d:95:0c:20:03:8d:3e:ee:7a:28:65:64:66:a4:d9:
- 83:ea:99:74:cd:6e:4d:7d:1c:eb:8d:b2:c5:af:16:1b:4e:c8:
- f3:55:ea:88:38:11:34:1d:11:af:3f:07:a8:4f:6a:d2:74:11:
- 2f:2a:fc:73:b7:5f:c2:15:43:05:6c:d6:7d:da:02:bd:22:9b:
- 4f:d3:f9:77
+ 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-----
-MIIDrDCCApSgAwIBAgIQCssoukZe5TkIdnRw883GEjANBgkqhkiG9w0BAQwFADBh
+MIIEFzCCAv+gAwIBAgIQB/LzXIeod6967+lHmTUlvTANBgkqhkiG9w0BAQwFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
-QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaMEwxCzAJBgNVBAYTAlVT
-MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJjAkBgNVBAMTHURpZ2lDZXJ0IEVDQyBT
-ZWN1cmUgU2VydmVyIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4ghC6nfYJN6g
-LGSkE85AnCNyqQIKDjc/ITa4jVMU9tWRlUvzlgKNcR7E2Munn17voOZ/WpIRllNv
-68DLP679Wz9HJOeaBy6Wvqgvu1cYr3GkvXg6HuhbPGtkESvMNCuMo4IBITCCAR0w
-EgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwNAYIKwYBBQUHAQEE
-KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQgYDVR0f
-BDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xv
-YmFsUm9vdENBLmNybDA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYc
-aHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAdBgNVHQ4EFgQUo53mH/naOU/A
-buiRy5Wl2jHiCp8wHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJ
-KoZIhvcNAQEMBQADggEBAMeKoENL7HTJxavVHzA1Nm6YVntIrAVjrnuaVyRXzG/6
-3qttnMe2uuzO58pzZNvfBDcKAEmzP58mrZGMIOgfiA4q+2Y3yDDo0sIkp0VILeoB
-UEoxlBPfjV/aKrtJPGHzecicZpIalir0ezZYoyxBEHQa0+1IttK7igZFcTMQMHp6
-mCHdJLnsnLWSB62DxsRq+HfmNb4TDydkskO/g+l3VtsIh5RHFPVfKK+jaEyDj2D3
-loB5hWp2Jp2VDCADjT7ueihlZGak2YPqmXTNbk19HOuNssWvFhtOyPNV6og4ETQd
-Ea8/B6hPatJ0ES8q/HO3X8IVQwVs1n3aAr0im0/T+Xc=
+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:
diff --git a/certs/E1.pem b/certs/E1.pem
index 4c3c212..a62fc03 100644
--- a/certs/E1.pem
+++ b/certs/E1.pem
@@ -122,122 +122,3 @@ 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 1O1.pem b/certs/GTS CA 1O1.pem
deleted file mode 100644
index ccdba4d..0000000
--- a/certs/GTS CA 1O1.pem
+++ /dev/null
@@ -1,186 +0,0 @@
-Certificate:
- Data:
- Version: 3 (0x2)
- Serial Number:
- 01:e3:b4:9a:a1:8d:8a:a9:81:25:69:50:b8
- Signature Algorithm: sha256WithRSAEncryption
- Issuer: OU = GlobalSign Root CA - R2, O = GlobalSign, CN = GlobalSign
- Validity
- Not Before: Jun 15 00:00:42 2017 GMT
- Not After : Dec 15 00:00:42 2021 GMT
- Subject: C = US, O = Google Trust Services, CN = GTS CA 1O1
- Subject Public Key Info:
- Public Key Algorithm: rsaEncryption
- RSA Public-Key: (2048 bit)
- Modulus:
- 00:d0:18:cf:45:d4:8b:cd:d3:9c:e4:40:ef:7e:b4:
- dd:69:21:1b:c9:cf:3c:8e:4c:75:b9:0f:31:19:84:
- 3d:9e:3c:29:ef:50:0d:10:93:6f:05:80:80:9f:2a:
- a0:bd:12:4b:02:e1:3d:9f:58:16:24:fe:30:9f:0b:
- 74:77:55:93:1d:4b:f7:4d:e1:92:82:10:f6:51:ac:
- 0c:c3:b2:22:94:0f:34:6b:98:10:49:e7:0b:9d:83:
- 39:dd:20:c6:1c:2d:ef:d1:18:61:65:e7:23:83:20:
- a8:23:12:ff:d2:24:7f:d4:2f:e7:44:6a:5b:4d:d7:
- 50:66:b0:af:9e:42:63:05:fb:e0:1c:c4:63:61:af:
- 9f:6a:33:ff:62:97:bd:48:d9:d3:7c:14:67:dc:75:
- dc:2e:69:e8:f8:6d:78:69:d0:b7:10:05:b8:f1:31:
- c2:3b:24:fd:1a:33:74:f8:23:e0:ec:6b:19:8a:16:
- c6:e3:cd:a4:cd:0b:db:b3:a4:59:60:38:88:3b:ad:
- 1d:b9:c6:8c:a7:53:1b:fc:bc:d9:a4:ab:bc:dd:3c:
- 61:d7:93:15:98:ee:81:bd:8f:e2:64:47:20:40:06:
- 4e:d7:ac:97:e8:b9:c0:59:12:a1:49:25:23:e4:ed:
- 70:34:2c:a5:b4:63:7c:f9:a3:3d:83:d1:cd:6d:24:
- ac:07
- 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:
- 98:D1:F8:6E:10:EB:CF:9B:EC:60:9F:18:90:1B:A0:EB:7D:09:FD:2B
- X509v3 Authority Key Identifier:
- keyid:9B:E2:07:57:67:1C:1E:C0:6A:06:DE:59:B4:9A:2D:DF:DC:19:86:2E
-
- Authority Information Access:
- OCSP - URI:http://ocsp.pki.goog/gsr2
-
- X509v3 CRL Distribution Points:
-
- Full Name:
- URI:http://crl.pki.goog/gsr2/gsr2.crl
-
- X509v3 Certificate Policies:
- Policy: 2.23.140.1.2.2
- CPS: https://pki.goog/repository/
-
- Signature Algorithm: sha256WithRSAEncryption
- 1a:80:3e:36:79:fb:f3:2e:a9:46:37:7d:5e:54:16:35:ae:c7:
- 4e:08:99:fe:bd:d1:34:69:26:52:66:07:3d:0a:ba:49:cb:62:
- f4:f1:1a:8e:fc:11:4f:68:96:4c:74:2b:d3:67:de:b2:a3:aa:
- 05:8d:84:4d:4c:20:65:0f:a5:96:da:0d:16:f8:6c:3b:db:6f:
- 04:23:88:6b:3a:6c:c1:60:bd:68:9f:71:8e:ee:2d:58:34:07:
- f0:d5:54:e9:86:59:fd:7b:5e:0d:21:94:f5:8c:c9:a8:f8:d8:
- f2:ad:cc:0f:1a:f3:9a:a7:a9:04:27:f9:a3:c9:b0:ff:02:78:
- 6b:61:ba:c7:35:2b:e8:56:fa:4f:c3:1c:0c:ed:b6:3c:b4:4b:
- ea:ed:cc:e1:3c:ec:dc:0d:8c:d6:3e:9b:ca:42:58:8b:cc:16:
- 21:17:40:bc:a2:d6:66:ef:da:c4:15:5b:cd:89:aa:9b:09:26:
- e7:32:d2:0d:6e:67:20:02:5b:10:b0:90:09:9c:0c:1f:9e:ad:
- d8:3b:ea:a1:fc:6c:e8:10:5c:08:52:19:51:2a:71:bb:ac:7a:
- b5:dd:15:ed:2b:c9:08:2a:2c:8a:b4:a6:21:ab:63:ff:d7:52:
- 49:50:d0:89:b7:ad:f2:af:fb:50:ae:2f:e1:95:0d:f3:46:ad:
- 9d:9c:f5:ca
------BEGIN CERTIFICATE-----
-MIIESjCCAzKgAwIBAgINAeO0mqGNiqmBJWlQuDANBgkqhkiG9w0BAQsFADBMMSAw
-HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs
-U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy
-MTUwMDAwNDJaMEIxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3Qg
-U2VydmljZXMxEzARBgNVBAMTCkdUUyBDQSAxTzEwggEiMA0GCSqGSIb3DQEBAQUA
-A4IBDwAwggEKAoIBAQDQGM9F1IvN05zkQO9+tN1pIRvJzzyOTHW5DzEZhD2ePCnv
-UA0Qk28FgICfKqC9EksC4T2fWBYk/jCfC3R3VZMdS/dN4ZKCEPZRrAzDsiKUDzRr
-mBBJ5wudgzndIMYcLe/RGGFl5yODIKgjEv/SJH/UL+dEaltN11BmsK+eQmMF++Ac
-xGNhr59qM/9il71I2dN8FGfcddwuaej4bXhp0LcQBbjxMcI7JP0aM3T4I+DsaxmK
-FsbjzaTNC9uzpFlgOIg7rR25xoynUxv8vNmkq7zdPGHXkxWY7oG9j+JkRyBABk7X
-rJfoucBZEqFJJSPk7XA0LKW0Y3z5oz2D0c1tJKwHAgMBAAGjggEzMIIBLzAOBgNV
-HQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1Ud
-EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFJjR+G4Q68+b7GCfGJAboOt9Cf0rMB8G
-A1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYuMDUGCCsGAQUFBwEBBCkwJzAl
-BggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdvb2cvZ3NyMjAyBgNVHR8EKzAp
-MCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dzcjIvZ3NyMi5jcmwwPwYDVR0g
-BDgwNjA0BgZngQwBAgIwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly9wa2kuZ29vZy9y
-ZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAGoA+Nnn78y6pRjd9XlQWNa7H
-TgiZ/r3RNGkmUmYHPQq6Scti9PEajvwRT2iWTHQr02fesqOqBY2ETUwgZQ+lltoN
-FvhsO9tvBCOIazpswWC9aJ9xju4tWDQH8NVU6YZZ/XteDSGU9YzJqPjY8q3MDxrz
-mqepBCf5o8mw/wJ4a2G6xzUr6Fb6T8McDO22PLRL6u3M4Tzs3A2M1j6bykJYi8wW
-IRdAvKLWZu/axBVbzYmqmwkm5zLSDW5nIAJbELCQCZwMH56t2Dvqofxs6BBcCFIZ
-USpxu6x6td0V7SvJCCosirSmIatj/9dSSVDQibet8q/7UK4v4ZUN80atnZz1yg==
------END CERTIFICATE-----
-Certificate:
- Data:
- Version: 3 (0x2)
- Serial Number:
- 04:00:00:00:00:01:0f:86:26:e6:0d
- Signature Algorithm: sha1WithRSAEncryption
- Issuer: OU = GlobalSign Root CA - R2, O = GlobalSign, CN = GlobalSign
- Validity
- Not Before: Dec 15 08:00:00 2006 GMT
- Not After : Dec 15 08:00:00 2021 GMT
- Subject: OU = GlobalSign Root CA - R2, O = GlobalSign, CN = GlobalSign
- Subject Public Key Info:
- Public Key Algorithm: rsaEncryption
- RSA Public-Key: (2048 bit)
- Modulus:
- 00:a6:cf:24:0e:be:2e:6f:28:99:45:42:c4:ab:3e:
- 21:54:9b:0b:d3:7f:84:70:fa:12:b3:cb:bf:87:5f:
- c6:7f:86:d3:b2:30:5c:d6:fd:ad:f1:7b:dc:e5:f8:
- 60:96:09:92:10:f5:d0:53:de:fb:7b:7e:73:88:ac:
- 52:88:7b:4a:a6:ca:49:a6:5e:a8:a7:8c:5a:11:bc:
- 7a:82:eb:be:8c:e9:b3:ac:96:25:07:97:4a:99:2a:
- 07:2f:b4:1e:77:bf:8a:0f:b5:02:7c:1b:96:b8:c5:
- b9:3a:2c:bc:d6:12:b9:eb:59:7d:e2:d0:06:86:5f:
- 5e:49:6a:b5:39:5e:88:34:ec:bc:78:0c:08:98:84:
- 6c:a8:cd:4b:b4:a0:7d:0c:79:4d:f0:b8:2d:cb:21:
- ca:d5:6c:5b:7d:e1:a0:29:84:a1:f9:d3:94:49:cb:
- 24:62:91:20:bc:dd:0b:d5:d9:cc:f9:ea:27:0a:2b:
- 73:91:c6:9d:1b:ac:c8:cb:e8:e0:a0:f4:2f:90:8b:
- 4d:fb:b0:36:1b:f6:19:7a:85:e0:6d:f2:61:13:88:
- 5c:9f:e0:93:0a:51:97:8a:5a:ce:af:ab:d5:f7:aa:
- 09:aa:60:bd:dc:d9:5f:df:72:a9:60:13:5e:00:01:
- c9:4a:fa:3f:a4:ea:07:03:21:02:8e:82:ca:03:c2:
- 9b:8f
- Exponent: 65537 (0x10001)
- X509v3 extensions:
- X509v3 Key Usage: critical
- Certificate Sign, CRL Sign
- X509v3 Basic Constraints: critical
- CA:TRUE
- X509v3 Subject Key Identifier:
- 9B:E2:07:57:67:1C:1E:C0:6A:06:DE:59:B4:9A:2D:DF:DC:19:86:2E
- X509v3 CRL Distribution Points:
-
- Full Name:
- URI:http://crl.globalsign.net/root-r2.crl
-
- X509v3 Authority Key Identifier:
- keyid:9B:E2:07:57:67:1C:1E:C0:6A:06:DE:59:B4:9A:2D:DF:DC:19:86:2E
-
- Signature Algorithm: sha1WithRSAEncryption
- 99:81:53:87:1c:68:97:86:91:ec:e0:4a:b8:44:0b:ab:81:ac:
- 27:4f:d6:c1:b8:1c:43:78:b3:0c:9a:fc:ea:2c:3c:6e:61:1b:
- 4d:4b:29:f5:9f:05:1d:26:c1:b8:e9:83:00:62:45:b6:a9:08:
- 93:b9:a9:33:4b:18:9a:c2:f8:87:88:4e:db:dd:71:34:1a:c1:
- 54:da:46:3f:e0:d3:2a:ab:6d:54:22:f5:3a:62:cd:20:6f:ba:
- 29:89:d7:dd:91:ee:d3:5c:a2:3e:a1:5b:41:f5:df:e5:64:43:
- 2d:e9:d5:39:ab:d2:a2:df:b7:8b:d0:c0:80:19:1c:45:c0:2d:
- 8c:e8:f8:2d:a4:74:56:49:c5:05:b5:4f:15:de:6e:44:78:39:
- 87:a8:7e:bb:f3:79:18:91:bb:f4:6f:9d:c1:f0:8c:35:8c:5d:
- 01:fb:c3:6d:b9:ef:44:6d:79:46:31:7e:0a:fe:a9:82:c1:ff:
- ef:ab:6e:20:c4:50:c9:5f:9d:4d:9b:17:8c:0c:e5:01:c9:a0:
- 41:6a:73:53:fa:a5:50:b4:6e:25:0f:fb:4c:18:f4:fd:52:d9:
- 8e:69:b1:e8:11:0f:de:88:d8:fb:1d:49:f7:aa:de:95:cf:20:
- 78:c2:60:12:db:25:40:8c:6a:fc:7e:42:38:40:64:12:f7:9e:
- 81:e1:93:2e
------BEGIN CERTIFICATE-----
-MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
-A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
-Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
-MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
-A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
-hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
-v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
-eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
-tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
-C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
-zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
-mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
-V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
-bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
-3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
-J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
-291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
-ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
-AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
-TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
------END CERTIFICATE-----
diff --git a/certs/GTS-CA-1C3.pem b/certs/GTS-CA-1C3.pem
new file mode 100644
index 0000000..a8432d2
--- /dev/null
+++ b/certs/GTS-CA-1C3.pem
@@ -0,0 +1,242 @@
+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-CA-1P5.pem b/certs/GTS-CA-1P5.pem
new file mode 100644
index 0000000..5be738d
--- /dev/null
+++ b/certs/GTS-CA-1P5.pem
@@ -0,0 +1,238 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 02:03:bc:50:a3:27:53:f0:91:80:22:ed:f1
+ 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 1P5
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b3:82:f0:24:8c:bf:2d:87:af:b2:d9:a7:ae:fa:
+ ca:ba:44:d6:5b:3e:fe:b2:f7:b2:65:16:dc:de:10:
+ e8:4f:2d:10:58:5a:28:86:87:a1:ee:6a:b3:a0:d9:
+ 75:4f:7f:a1:52:01:8b:55:a8:4a:5b:06:48:c8:36:
+ 12:25:ab:89:f9:f2:23:5f:9d:60:65:f9:5c:da:be:
+ 3a:e8:5c:6d:7d:9c:d0:84:18:85:30:cd:4e:9b:ec:
+ 3c:d8:b3:e1:96:d4:f3:c5:0b:65:db:8f:b0:74:cb:
+ f6:1e:f3:78:f1:ac:95:c5:dd:73:c3:31:88:81:af:
+ 74:aa:6f:fd:0c:e3:05:95:f0:c5:10:4f:65:63:fa:
+ a0:af:c6:18:3d:c5:a1:df:97:79:d7:05:89:b3:30:
+ b0:74:ae:3d:92:10:6b:8c:15:77:dd:0b:04:57:fb:
+ 81:03:dd:ea:22:34:d5:e5:56:b2:f0:c4:8d:41:b1:
+ c3:02:db:62:ec:80:d0:ff:76:d4:86:e4:04:1a:b6:
+ b6:0c:2b:62:71:7d:d9:af:d9:f1:5e:fa:c0:1e:ca:
+ a0:19:5c:55:f0:80:d1:2a:0c:07:86:90:9f:35:e3:
+ 28:2b:5b:ef:23:c8:a3:1d:a4:a3:3a:ee:fe:83:dc:
+ 82:4c:25:b0:4d:c5:51:ad:9e:9b:d3:5b:84:c2:1a:
+ 5a:e9
+ 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:
+ D5:FC:9E:0D:DF:1E:CA:DD:08:97:97:6E:2B:C5:5F:C5:2B:F5:EC:B8
+ X509v3 Authority Key Identifier:
+ 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
+ Signature Algorithm: sha256WithRSAEncryption
+ Signature Value:
+ 6c:63:27:ee:23:df:e5:52:68:4d:81:66:91:85:df:7d:65:e5:
+ 5b:37:31:08:26:b2:07:5d:9a:be:b1:ca:01:b9:ad:bf:9d:77:
+ f6:51:1d:d7:98:c5:0b:49:a1:7b:a1:d7:d3:68:e5:44:0f:8b:
+ ba:36:dd:42:82:77:d2:8d:dd:f5:3f:fb:eb:c8:07:98:93:ee:
+ 5a:d0:b5:3d:de:4b:1c:2d:8c:4d:ec:7e:8c:7b:fe:4e:40:fd:
+ f0:b4:b3:59:02:10:51:5c:e3:c0:2b:fd:b7:06:48:51:7e:09:
+ 5e:3f:0f:dc:a7:fe:97:e7:79:c5:0e:44:89:78:c5:69:59:29:
+ a0:9a:3a:48:36:29:a6:94:93:55:2d:b8:47:b5:e9:96:b5:9f:
+ 07:cd:a6:ab:3e:32:8a:c0:86:83:c5:c1:41:c8:9f:2f:35:8e:
+ 0d:c0:07:7a:e1:ac:c9:65:b5:cb:8a:a7:dd:71:d8:61:65:39:
+ 84:ac:32:3e:f7:7a:36:f1:56:9f:57:a9:41:6d:5a:90:a7:db:
+ 3a:ea:75:80:0c:63:0b:69:74:6f:07:4c:15:f3:37:28:a5:19:
+ a4:6e:f5:f6:20:cd:63:b2:7e:c4:2b:09:75:89:da:d1:3c:2e:
+ 72:4f:36:1a:a1:9e:44:d0:cd:9b:a6:23:08:3f:97:a1:a7:9e:
+ 5a:a5:f7:09:94:ad:5d:76:5d:28:56:d1:1a:66:51:51:07:7b:
+ de:3d:b0:c8:ef:30:7a:24:2d:be:b8:b3:86:f6:4b:f7:f0:b5:
+ 4f:ff:ce:c6:f9:f6:3f:2a:27:08:0f:09:3e:23:5a:c7:e3:42:
+ 2d:7a:36:e4:3d:98:96:60:39:98:ea:d1:db:63:2a:eb:78:09:
+ b1:4e:21:b3:8e:b7:ce:3e:92:f1:95:5c:a4:39:d0:c0:2b:c8:
+ 53:15:f5:d2:2f:82:cd:06:74:67:99:90:77:37:0a:97:2d:c5:
+ 1c:1e:f4:d0:5b:e9:15:e3:ea:02:09:c8:13:d7:13:70:65:bf:
+ fb:88:9b:5a:25:be:77:09:e1:a7:6a:4e:11:75:b9:1e:4d:f1:
+ 00:1b:6a:66:79:8e:c3:6e:d8:6d:a2:22:a2:6d:05:fb:2c:f2:
+ f1:50:e5:a0:d1:d8:9f:35:7d:fc:70:ab:59:2a:02:f1:be:b0:
+ d3:f1:f8:cd:12:b9:6a:25:90:5b:e3:85:20:e6:f5:da:cb:40:
+ 1c:19:34:20:03:61:77:ba:7f:48:0f:49:0b:29:eb:e7:61:64:
+ c7:63:d1:47:eb:1c:e1:ee:94:46:ef:39:73:cc:ee:4f:2b:8d:
+ dc:fb:58:a7:b3:65:20:99:95:b9:fb:55:6f:d7:96:6e:94:3d:
+ f4:7a:92:8e:63:1d:df:6d
+-----BEGIN CERTIFICATE-----
+MIIFjDCCA3SgAwIBAgINAgO8UKMnU/CRgCLt8TANBgkqhkiG9w0BAQsFADBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMjAwODEzMDAwMDQyWhcNMjcwOTMwMDAw
+MDQyWjBGMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzETMBEGA1UEAxMKR1RTIENBIDFQNTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBALOC8CSMvy2Hr7LZp676yrpE1ls+/rL3smUW3N4Q6E8tEFha
+KIaHoe5qs6DZdU9/oVIBi1WoSlsGSMg2EiWrifnyI1+dYGX5XNq+OuhcbX2c0IQY
+hTDNTpvsPNiz4ZbU88ULZduPsHTL9h7zePGslcXdc8MxiIGvdKpv/QzjBZXwxRBP
+ZWP6oK/GGD3Fod+XedcFibMwsHSuPZIQa4wVd90LBFf7gQPd6iI01eVWsvDEjUGx
+wwLbYuyA0P921IbkBBq2tgwrYnF92a/Z8V76wB7KoBlcVfCA0SoMB4aQnzXjKCtb
+7yPIox2kozru/oPcgkwlsE3FUa2em9NbhMIaWukCAwEAAaOCAXYwggFyMA4GA1Ud
+DwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0T
+AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU1fyeDd8eyt0Il5duK8VfxSv17LgwHwYD
+VR0jBBgwFoAU5K8rJnEaK0gnhS9SZizv8IkTcT4waAYIKwYBBQUHAQEEXDBaMCYG
+CCsGAQUFBzABhhpodHRwOi8vb2NzcC5wa2kuZ29vZy9ndHNyMTAwBggrBgEFBQcw
+AoYkaHR0cDovL3BraS5nb29nL3JlcG8vY2VydHMvZ3RzcjEuZGVyMDQGA1UdHwQt
+MCswKaAnoCWGI2h0dHA6Ly9jcmwucGtpLmdvb2cvZ3RzcjEvZ3RzcjEuY3JsME0G
+A1UdIARGMEQwOAYKKwYBBAHWeQIFAzAqMCgGCCsGAQUFBwIBFhxodHRwczovL3Br
+aS5nb29nL3JlcG9zaXRvcnkvMAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAgEA
+bGMn7iPf5VJoTYFmkYXffWXlWzcxCCayB12avrHKAbmtv5139lEd15jFC0mhe6HX
+02jlRA+LujbdQoJ30o3d9T/768gHmJPuWtC1Pd5LHC2MTex+jHv+TkD98LSzWQIQ
+UVzjwCv9twZIUX4JXj8P3Kf+l+d5xQ5EiXjFaVkpoJo6SDYpppSTVS24R7XplrWf
+B82mqz4yisCGg8XBQcifLzWODcAHeuGsyWW1y4qn3XHYYWU5hKwyPvd6NvFWn1ep
+QW1akKfbOup1gAxjC2l0bwdMFfM3KKUZpG719iDNY7J+xCsJdYna0Twuck82GqGe
+RNDNm6YjCD+XoaeeWqX3CZStXXZdKFbRGmZRUQd73j2wyO8weiQtvrizhvZL9/C1
+T//Oxvn2PyonCA8JPiNax+NCLXo25D2YlmA5mOrR22Mq63gJsU4hs463zj6S8ZVc
+pDnQwCvIUxX10i+CzQZ0Z5mQdzcKly3FHB700FvpFePqAgnIE9cTcGW/+4ibWiW+
+dwnhp2pOEXW5Hk3xABtqZnmOw27YbaIiom0F+yzy8VDloNHYnzV9/HCrWSoC8b6w
+0/H4zRK5aiWQW+OFIOb12stAHBk0IANhd7p/SA9JCynr52Fkx2PRR+sc4e6URu85
+c8zuTyuN3PtYp7NlIJmVuftVb9eWbpQ99HqSjmMd320=
+-----END CERTIFICATE-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 02:03:e5:93:6f:31:b0:13:49:88:6b:a2:17
+ 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
+ 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
+ Digital Signature, 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
+ Signature Value:
+ 9f:aa:42:26:db:0b:9b:be:ff:1e:96:92:2e:3e:a2:65:4a:6a:
+ 98:ba:22:cb:7d:c1:3a:d8:82:0a:06:c6:f6:a5:de:c0:4e:87:
+ 66:79:a1:f9:a6:58:9c:aa:f9:b5:e6:60:e7:e0:e8:b1:1e:42:
+ 41:33:0b:37:3d:ce:89:70:15:ca:b5:24:a8:cf:6b:b5:d2:40:
+ 21:98:cf:22:34:cf:3b:c5:22:84:e0:c5:0e:8a:7c:5d:88:e4:
+ 35:24:ce:9b:3e:1a:54:1e:6e:db:b2:87:a7:fc:f3:fa:81:55:
+ 14:62:0a:59:a9:22:05:31:3e:82:d6:ee:db:57:34:bc:33:95:
+ d3:17:1b:e8:27:a2:8b:7b:4e:26:1a:7a:5a:64:b6:d1:ac:37:
+ f1:fd:a0:f3:38:ec:72:f0:11:75:9d:cb:34:52:8d:e6:76:6b:
+ 17:c6:df:86:ab:27:8e:49:2b:75:66:81:10:21:a6:ea:3e:f4:
+ ae:25:ff:7c:15:de:ce:8c:25:3f:ca:62:70:0a:f7:2f:09:66:
+ 07:c8:3f:1c:fc:f0:db:45:30:df:62:88:c1:b5:0f:9d:c3:9f:
+ 4a:de:59:59:47:c5:87:22:36:e6:82:a7:ed:0a:b9:e2:07:a0:
+ 8d:7b:7a:4a:3c:71:d2:e2:03:a1:1f:32:07:dd:1b:e4:42:ce:
+ 0c:00:45:61:80:b5:0b:20:59:29:78:bd:f9:55:cb:63:c5:3c:
+ 4c:f4:b6:ff:db:6a:5f:31:6b:99:9e:2c:c1:6b:50:a4:d7:e6:
+ 18:14:bd:85:3f:67:ab:46:9f:a0:ff:42:a7:3a:7f:5c:cb:5d:
+ b0:70:1d:2b:34:f5:d4:76:09:0c:eb:78:4c:59:05:f3:33:42:
+ c3:61:15:10:1b:77:4d:ce:22:8c:d4:85:f2:45:7d:b7:53:ea:
+ ef:40:5a:94:0a:5c:20:5f:4e:40:5d:62:22:76:df:ff:ce:61:
+ bd:8c:23:78:d2:37:02:e0:8e:de:d1:11:37:89:f6:bf:ed:49:
+ 07:62:ae:92:ec:40:1a:af:14:09:d9:d0:4e:b2:a2:f7:be:ee:
+ ee:d8:ff:dc:1a:2d:de:b8:36:71:e2:fc:79:b7:94:25:d1:48:
+ 73:5b:a1:35:e7:b3:99:67:75:c1:19:3a:2b:47:4e:d3:42:8e:
+ fd:31:c8:16:66:da:d2:0c:3c:db:b3:8e:c9:a1:0d:80:0f:7b:
+ 16:77:14:bf:ff:db:09:94:b2:93:bc:20:58:15:e9:db:71:43:
+ f3:de:10:c3:00:dc:a8:2a:95:b6:c2:d6:3f:90:6b:76:db:6c:
+ fe:8c:bc:f2:70:35:0c:dc:99:19:35:dc:d7:c8:46:63:d5:36:
+ 71:ae:57:fb:b7:82:6d:dc
+-----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/GlobalSign-Atlas-R3-DV-TLS-CA-2022-Q3.pem b/certs/GlobalSign-Atlas-R3-DV-TLS-CA-2022-Q3.pem
new file mode 100644
index 0000000..b514c11
--- /dev/null
+++ b/certs/GlobalSign-Atlas-R3-DV-TLS-CA-2022-Q3.pem
@@ -0,0 +1,177 @@
+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
index 4faba90..4faba90 100644
--- a/certs/Go Daddy Secure Certificate Authority - G2.pem
+++ b/certs/Go-Daddy-Secure-Certificate-Authority-G2.pem
diff --git a/certs/R3.pem b/certs/R3.pem
index ac322ec..837b709 100644
--- a/certs/R3.pem
+++ b/certs/R3.pem
@@ -235,80 +235,3 @@ oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
-Certificate:
- Data:
- Version: 3 (0x2)
- Serial Number:
- 44:af:b0:80:d6:a3:27:ba:89:30:39:86:2e:f8:40:6b
- Signature Algorithm: sha1WithRSAEncryption
- Issuer: O = Digital Signature Trust Co., CN = DST Root CA X3
- Validity
- Not Before: Sep 30 21:12:19 2000 GMT
- Not After : Sep 30 14:01:15 2021 GMT
- Subject: O = Digital Signature Trust Co., CN = DST Root CA X3
- Subject Public Key Info:
- Public Key Algorithm: rsaEncryption
- RSA Public-Key: (2048 bit)
- Modulus:
- 00:df:af:e9:97:50:08:83:57:b4:cc:62:65:f6:90:
- 82:ec:c7:d3:2c:6b:30:ca:5b:ec:d9:c3:7d:c7:40:
- c1:18:14:8b:e0:e8:33:76:49:2a:e3:3f:21:49:93:
- ac:4e:0e:af:3e:48:cb:65:ee:fc:d3:21:0f:65:d2:
- 2a:d9:32:8f:8c:e5:f7:77:b0:12:7b:b5:95:c0:89:
- a3:a9:ba:ed:73:2e:7a:0c:06:32:83:a2:7e:8a:14:
- 30:cd:11:a0:e1:2a:38:b9:79:0a:31:fd:50:bd:80:
- 65:df:b7:51:63:83:c8:e2:88:61:ea:4b:61:81:ec:
- 52:6b:b9:a2:e2:4b:1a:28:9f:48:a3:9e:0c:da:09:
- 8e:3e:17:2e:1e:dd:20:df:5b:c6:2a:8a:ab:2e:bd:
- 70:ad:c5:0b:1a:25:90:74:72:c5:7b:6a:ab:34:d6:
- 30:89:ff:e5:68:13:7b:54:0b:c8:d6:ae:ec:5a:9c:
- 92:1e:3d:64:b3:8c:c6:df:bf:c9:41:70:ec:16:72:
- d5:26:ec:38:55:39:43:d0:fc:fd:18:5c:40:f1:97:
- eb:d5:9a:9b:8d:1d:ba:da:25:b9:c6:d8:df:c1:15:
- 02:3a:ab:da:6e:f1:3e:2e:f5:5c:08:9c:3c:d6:83:
- 69:e4:10:9b:19:2a:b6:29:57:e3:e5:3d:9b:9f:f0:
- 02:5d
- Exponent: 65537 (0x10001)
- X509v3 extensions:
- X509v3 Basic Constraints: critical
- CA:TRUE
- X509v3 Key Usage: critical
- Certificate Sign, CRL Sign
- X509v3 Subject Key Identifier:
- C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10
- Signature Algorithm: sha1WithRSAEncryption
- a3:1a:2c:9b:17:00:5c:a9:1e:ee:28:66:37:3a:bf:83:c7:3f:
- 4b:c3:09:a0:95:20:5d:e3:d9:59:44:d2:3e:0d:3e:bd:8a:4b:
- a0:74:1f:ce:10:82:9c:74:1a:1d:7e:98:1a:dd:cb:13:4b:b3:
- 20:44:e4:91:e9:cc:fc:7d:a5:db:6a:e5:fe:e6:fd:e0:4e:dd:
- b7:00:3a:b5:70:49:af:f2:e5:eb:02:f1:d1:02:8b:19:cb:94:
- 3a:5e:48:c4:18:1e:58:19:5f:1e:02:5a:f0:0c:f1:b1:ad:a9:
- dc:59:86:8b:6e:e9:91:f5:86:ca:fa:b9:66:33:aa:59:5b:ce:
- e2:a7:16:73:47:cb:2b:cc:99:b0:37:48:cf:e3:56:4b:f5:cf:
- 0f:0c:72:32:87:c6:f0:44:bb:53:72:6d:43:f5:26:48:9a:52:
- 67:b7:58:ab:fe:67:76:71:78:db:0d:a2:56:14:13:39:24:31:
- 85:a2:a8:02:5a:30:47:e1:dd:50:07:bc:02:09:90:00:eb:64:
- 63:60:9b:16:bc:88:c9:12:e6:d2:7d:91:8b:f9:3d:32:8d:65:
- b4:e9:7c:b1:57:76:ea:c5:b6:28:39:bf:15:65:1c:c8:f6:77:
- 96:6a:0a:8d:77:0b:d8:91:0b:04:8e:07:db:29:b6:0a:ee:9d:
- 82:35:35:10
------BEGIN CERTIFICATE-----
-MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
-MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
-DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
-PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
-Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
-AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
-rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
-OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
-xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
-7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
-aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
-HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
-SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
-ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
-AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
-R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
-JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
-Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
------END CERTIFICATE-----
diff --git a/certs/Starfield Secure Certificate Authority - G2.pem b/certs/Starfield-Secure-Certificate-Authority-G2.pem
index 7772e6b..7772e6b 100644
--- a/certs/Starfield Secure Certificate Authority - G2.pem
+++ b/certs/Starfield-Secure-Certificate-Authority-G2.pem
diff --git a/check-certificates b/check-certificates
deleted file mode 100644
index 68be7ee..0000000
--- a/check-certificates
+++ /dev/null
@@ -1,132 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: check-certificates
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# check for certificate validity
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-certificates.md
-
-:local 0 "check-certificates";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global CertRenewPass;
-:global CertRenewTime;
-:global CertRenewUrl;
-:global Identity;
-
-:global CertificateAvailable
-:global CertificateNameByCN;
-:global IfThenElse;
-:global LogPrintExit2;
-:global ParseKeyValueStore;
-:global SendNotification;
-:global SymbolForNotification;
-:global UrlEncode;
-:global WaitForFile;
-:global WaitFullyConnected;
-
-:local FormatExpire do={
- :global CharacterReplace;
- :return [ $CharacterReplace [ $CharacterReplace [ :tostr $1 ] "w" "w " ] "d" "d " ];
-}
-
-$WaitFullyConnected;
-
-:foreach Cert in=[ / certificate find where !revoked !ca !scep-url expires-after<$CertRenewTime ] do={
- :local CertVal [ / certificate get $Cert ];
-
- :do {
- :if ([ :len $CertRenewUrl ] = 0) do={
- $LogPrintExit2 info $0 ("No CertRenewUrl given.") true;
- }
-
- :foreach Type in={ ".pem"; ".p12" } do={
- :local CertFileName ([ $UrlEncode ($CertVal->"common-name") ] . $Type);
- :do {
- / tool fetch check-certificate=yes-without-crl \
- ($CertRenewUrl . $CertFileName) dst-path=$CertFileName as-value;
- $WaitForFile $CertFileName;
- :foreach PassPhrase in=$CertRenewPass do={
- / certificate import file-name=$CertFileName passphrase=$PassPhrase;
- }
- / file remove [ find where name=$CertFileName ];
-
- :foreach CertInChain in=[ / certificate find where name~("^" . $CertFileName . "_[0-9]+\$") common-name!=($CertVal->"common-name") ] do={
- $CertificateNameByCN [ / certificate get $CertInChain common-name ];
- }
- } on-error={
- $LogPrintExit2 debug $0 ("Could not download certificate file " . $CertFileName) false;
- }
- }
-
- :local CertNew [ / certificate find where common-name=($CertVal->"common-name") fingerprint!=[ :tostr ($CertVal->"fingerprint") ] expires-after>$CertRenewTime ];
- :local CertNewVal [ / certificate get $CertNew ];
-
- :if ([ $CertificateAvailable ([ $ParseKeyValueStore ($CertNewVal->"issuer") ]->"CN") ] = false) do={
- $LogPrintExit2 warning $0 ("The certificate chain is not available!") false;
- }
-
- :if ($Cert != $CertNew) do={
- $LogPrintExit2 debug $0 ("Certificate '" . $CertVal->"name" . "' was not updated, but replaced.") false;
-
- :if (($CertVal->"private-key") = true && ($CertVal->"private-key") != ($CertNewVal->"private-key")) do={
- / certificate remove $CertNew;
- $LogPrintExit2 warning $0 ("Old certificate '" . ($CertVal->"name") . "' has a private key, new certificate does not. Aborting renew.") true;
- }
-
- / ip service set certificate=($CertNewVal->"name") [ find where certificate=($CertVal->"name") ];
-
- :do {
- / ip ipsec identity set certificate=($CertNewVal->"name") [ / ip ipsec identity find where certificate=($CertVal->"name") ];
- / ip ipsec identity set remote-certificate=($CertNewVal->"name") [ / ip ipsec identity find where remote-certificate=($CertVal->"name") ];
- } on-error={
- $LogPrintExit2 debug $0 ("Setting IPSEC certificates failed. Package 'security' not installed?") false;
- }
-
- :do {
- / ip hotspot profile set ssl-certificate=($CertNewVal->"name") [ / ip hotspot profile find where ssl-certificate=($CertVal->"name") ];
- } on-error={
- $LogPrintExit2 debug $0 ("Setting hotspot certificates failed. Package 'hotspot' not installed?") false;
- }
-
- / certificate remove $Cert;
- / certificate set $CertNew name=($CertVal->"name");
- }
-
- $SendNotification ([ $SymbolForNotification "lock-with-ink-pen" ] . "Certificate renewed") \
- ("A certificate on " . $Identity . " has been renewed.\n\n" . \
- "Name: " . ($CertVal->"name") . "\n" . \
- "CommonName: " . ($CertNewVal->"common-name") . "\n" . \
- "Private key: " . [ $IfThenElse (($CertNewVal->"private-key") = true) "available" "missing" ] . "\n" . \
- "Fingerprint: " . ($CertNewVal->"fingerprint") . "\n" . \
- "Issuer: " . ([ $ParseKeyValueStore ($CertNewVal->"issuer") ]->"CN") . "\n" . \
- "Validity: " . ($CertNewVal->"invalid-before") . " to " . ($CertNewVal->"invalid-after") . "\n" . \
- "Expires in: " . [ $FormatExpire ($CertNewVal->"expires-after") ]) "" "true";
- $LogPrintExit2 info $0 ("The certificate " . ($CertVal->"name") . " has been renewed.") false;
- } on-error={
- $LogPrintExit2 debug $0 ("Could not renew certificate " . ($CertVal->"name") . ".") false;
- }
-}
-
-:foreach Cert in=[ / certificate find where !revoked !scep-url !(expires-after=[]) expires-after<2w !(fingerprint=[]) ] do={
- :local CertVal [ / certificate get $Cert ];
-
- :if ([ :len [ / certificate scep-server find where ca-cert=($CertVal->"ca") ] ] > 0) do={
- $LogPrintExit2 debug $0 ("Certificate \"" . ($CertVal->"name") . "\" is handled by SCEP, skipping.") false;
- } else={
- :local State [ $IfThenElse (($CertVal->"expired") = true) "expired" "is about to expire" ];
-
- $SendNotification ([ $SymbolForNotification "warning-sign" ] . "Certificate warning!") \
- ("A certificate on " . $Identity . " " . $State . ".\n\n" . \
- "Name: " . ($CertVal->"name") . "\n" . \
- "CommonName: " . ($CertVal->"common-name") . "\n" . \
- "Private key: " . [ $IfThenElse (($CertNewVal->"private-key") = true) "available" "missing" ] . "\n" . \
- "Fingerprint: " . ($CertVal->"fingerprint") . "\n" . \
- "Issuer: " . ($CertVal->"ca") . ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") . "\n" . \
- "Validity: " . ($CertVal->"invalid-before") . " to " . ($CertVal->"invalid-after") . "\n" . \
- "Expires in: " . [ $IfThenElse (($CertVal->"expired") = true) "expired" [ $FormatExpire ($CertVal->"expires-after") ] ]);
- $LogPrintExit2 info $0 ("The certificate " . ($CertVal->"name") . " " . $State . \
- ", it is invalid after " . ($CertVal->"invalid-after") . ".") false;
- }
-}
diff --git a/check-certificates.rsc b/check-certificates.rsc
new file mode 100644
index 0000000..ab78e22
--- /dev/null
+++ b/check-certificates.rsc
@@ -0,0 +1,222 @@
+#!rsc by RouterOS
+# RouterOS script: check-certificates
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# check for certificate validity
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-certificates.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global CertRenewTime;
+ :global CertRenewUrl;
+ :global CertWarnTime;
+ :global Identity;
+
+ :global CertificateAvailable
+ :global EscapeForRegEx;
+ :global IfThenElse;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global ScriptLock;
+ :global SendNotification2;
+ :global SymbolForNotification;
+ :global UrlEncode;
+ :global WaitFullyConnected;
+
+ :local CheckCertificatesDownloadImport do={
+ :local ScriptName [ :tostr $1 ];
+ :local Name [ :tostr $2 ];
+
+ :global CertRenewUrl;
+ :global CertRenewPass;
+
+ :global CertificateNameByCN;
+ :global EscapeForRegEx;
+ :global FetchUserAgent;
+ :global LogPrint;
+ :global UrlEncode;
+ :global WaitForFile;
+
+ :local Return false;
+
+ :foreach Type in={ ".pem"; ".p12" } do={
+ :local CertFileName ([ $UrlEncode $Name ] . $Type);
+ :do {
+ /tool/fetch check-certificate=yes-without-crl http-header-field=({ [ $FetchUserAgent $ScriptName ] }) \
+ ($CertRenewUrl . $CertFileName) dst-path=$CertFileName as-value;
+ $WaitForFile $CertFileName;
+
+ :local DecryptionFailed true;
+ :foreach PassPhrase in=$CertRenewPass do={
+ :local Result [ /certificate/import file-name=$CertFileName passphrase=$PassPhrase as-value ];
+ :if ($Result->"decryption-failures" = 0) do={
+ :set DecryptionFailed false;
+ }
+ }
+ /file/remove [ find where name=$CertFileName ];
+
+ :if ($DecryptionFailed = true) do={
+ $LogPrint warning $ScriptName ("Decryption failed for certificate file '" . $CertFileName . "'.");
+ }
+
+ :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 ];
+ }
+
+ :set Return true;
+ } on-error={
+ $LogPrint debug $ScriptName ("Could not download certificate file '" . $CertFileName . "'.");
+ }
+ }
+
+ :return $Return;
+ }
+
+ :local FormatInfo do={
+ :local Cert $1;
+
+ :global FormatLine;
+ :global FormatMultiLines;
+ :global IfThenElse;
+
+ :local FormatExpire do={
+ :global CharacterReplace;
+ :return [ $CharacterReplace [ $CharacterReplace [ :tostr $1 ] "w" "w " ] "d" "d " ];
+ }
+
+ :local FormatCertChain do={
+ :local Cert $1;
+
+ :global EitherOr;
+ :global ParseKeyValueStore;
+
+ :local CertVal [ /certificate/get $Cert ];
+
+ :if ([ :typeof ($CertVal->"issuer") ] = "nothing") do={
+ :return "self-signed";
+ }
+
+ :local Return "";
+ :for I from=0 to=5 do={
+ :set Return ($Return . [ $EitherOr ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") \
+ ([ $ParseKeyValueStore (($CertVal->"issuer")->0) ]->"CN") ]);
+ :set CertVal [ /certificate/get [ find where skid=($CertVal->"akid") ] ];
+ :if (($CertVal->"akid") = "" || ($CertVal->"akid") = ($CertVal->"skid")) do={
+ :return $Return;
+ }
+ :set Return ($Return . " -> ");
+ }
+ :return ($Return . "...");
+ }
+
+ :local CertVal [ /certificate/get $Cert ];
+
+ :return ( \
+ [ $FormatLine "Name" ($CertVal->"name") ] . "\n" . \
+ [ $IfThenElse ([ :len ($CertVal->"common-name") ] > 0) ([ $FormatLine "CommonName" ($CertVal->"common-name") ] . "\n") ] . \
+ [ $IfThenElse ([ :len ($CertVal->"subject-alt-name") ] > 0) ([ $FormatMultiLines "SubjectAltNames" ($CertVal->"subject-alt-name") ] . "\n") ] . \
+ [ $FormatLine "Private key" [ $IfThenElse (($CertVal->"private-key") = true) "available" "missing" ] ] . "\n" . \
+ [ $FormatLine "Fingerprint" ($CertVal->"fingerprint") ] . "\n" . \
+ [ $IfThenElse ([ :len ($CertVal->"ca") ] > 0) [ $FormatLine "Issuer" ($CertVal->"ca") ] [ $FormatLine "Issuer chain" [ $FormatCertChain $Cert ] ] ] . "\n" . \
+ "Validity:\n" . \
+ [ $FormatLine " from" ($CertVal->"invalid-before") ] . "\n" . \
+ [ $FormatLine " to" ($CertVal->"invalid-after") ] . "\n" . \
+ [ $FormatLine "Expires in" [ $IfThenElse (($CertVal->"expired") = true) "expired" [ $FormatExpire ($CertVal->"expires-after") ] ] ]);
+ }
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+ $WaitFullyConnected;
+
+ :foreach Cert in=[ /certificate/find where !revoked !ca !scep-url expires-after<$CertRenewTime ] do={
+ :local CertVal [ /certificate/get $Cert ];
+ :local CertNew;
+ :local LastName;
+
+ :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 ImportSuccess [ $CheckCertificatesDownloadImport $ScriptName $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 $ScriptName $LastName ];
+ }
+ }
+ :if ($ImportSuccess = false) do={ :error false; }
+
+ :if ([ :len ($CertVal->"fingerprint") ] > 0 && $CertVal->"fingerprint" != [ /certificate/get $Cert fingerprint ]) do={
+ $LogPrint debug $ScriptName ("Certificate '" . $CertVal->"name" . "' was updated in place.");
+ :set CertVal [ /certificate/get $Cert ];
+ } else={
+ $LogPrint debug $ScriptName ("Certificate '" . $CertVal->"name" . "' was not updated, but replaced.");
+
+ :set 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 ([ $CertificateAvailable ([ $ParseKeyValueStore ($CertNewVal->"issuer") ]->"CN") ] = false) do={
+ $LogPrint warning $ScriptName ("The certificate chain is not available!");
+ }
+
+ :if (($CertVal->"private-key") = true && ($CertVal->"private-key") != ($CertNewVal->"private-key")) do={
+ /certificate/remove $CertNew;
+ $LogPrint warning $ScriptName ("Old certificate '" . ($CertVal->"name") . "' has a private key, new certificate does not. Aborting renew.");
+ :error false;
+ }
+
+ /ip/service/set certificate=($CertNewVal->"name") [ find where certificate=($CertVal->"name") ];
+
+ /ip/ipsec/identity/set certificate=($CertNewVal->"name") [ find where certificate=($CertVal->"name") ];
+ /ip/ipsec/identity/set remote-certificate=($CertNewVal->"name") [ find where remote-certificate=($CertVal->"name") ];
+
+ /ip/hotspot/profile/set ssl-certificate=($CertNewVal->"name") [ find where ssl-certificate=($CertVal->"name") ];
+
+ /certificate/remove $Cert;
+ /certificate/set $CertNew name=($CertVal->"name");
+ :set CertNewVal;
+ :set CertVal [ /certificate/get $CertNew ];
+ }
+
+ $SendNotification2 ({ origin=$ScriptName; silent=true; \
+ subject=([ $SymbolForNotification "lock-with-ink-pen" ] . "Certificate renewed: " . ($CertVal->"name")); \
+ message=("A certificate on " . $Identity . " has been renewed.\n\n" . [ $FormatInfo $CertNew ]) });
+ $LogPrint info $ScriptName ("The certificate '" . ($CertVal->"name") . "' has been renewed.");
+ } on-error={
+ $LogPrint debug $ScriptName ("Could not renew certificate '" . ($CertVal->"name") . "'.");
+ }
+ }
+
+ :foreach Cert in=[ /certificate/find where !revoked !scep-url !(expires-after=[]) \
+ expires-after<$CertWarnTime !(fingerprint=[]) ] do={
+ :local CertVal [ /certificate/get $Cert ];
+
+ :if ([ :len [ /certificate/scep-server/find where ca-cert=($CertVal->"ca") ] ] > 0) do={
+ $LogPrint debug $ScriptName ("Certificate '" . ($CertVal->"name") . "' is handled by SCEP, skipping.");
+ } else={
+ :local State [ $IfThenElse (($CertVal->"expired") = true) "expired" "is about to expire" ];
+
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "warning-sign" ] . "Certificate warning: " . ($CertVal->"name")); \
+ message=("A certificate on " . $Identity . " " . $State . ".\n\n" . [ $FormatInfo $Cert ]) });
+ $LogPrint info $ScriptName ("The certificate '" . ($CertVal->"name") . "' " . $State . \
+ ", it is invalid after " . ($CertVal->"invalid-after") . ".");
+ }
+ }
+} on-error={ }
diff --git a/check-health b/check-health
deleted file mode 100644
index 761a72f..0000000
--- a/check-health
+++ /dev/null
@@ -1,99 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: check-health
-# Copyright (c) 2019-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# check for RouterOS health state
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-health.md
-
-:local 0 "check-health";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global CheckHealthLast;
-:global CheckHealthTemperature;
-:global CheckHealthTemperatureDeviation;
-:global CheckHealthTemperatureNotified;
-:global CheckHealthVoltagePercent;
-:global Identity;
-
-:global LogPrintExit2;
-:global SendNotification;
-:global SymbolForNotification;
-
-:local FormatVoltage do={
- :local Voltage [ :tonum $1 ];
- :return (($Voltage / 10) . "." . [ :pick $Voltage ([ :len $Voltage ] - 1) ] . "V");
-}
-
-:local CheckHealthCurrent [ / system health get ];
-
-:if ([ :len $CheckHealthCurrent ] = 0) do={
- $LogPrintExit2 error $0 ("Your device does not provide any health values.") true;
-}
-
-:if ([ :typeof $CheckHealthTemperatureNotified ] != "array") do={
- :set CheckHealthTemperatureNotified [ :toarray "" ];
-}
-
-:foreach Name,Voltage in=$CheckHealthCurrent do={
- :if ($Name ~ "(battery|voltage)" && \
- [ :typeof ($CheckHealthLast->$Name) ] = "num" && \
- [ :typeof $Voltage ] = "num") do={
- :if ($CheckHealthLast->$Name * (100 + $CheckHealthVoltagePercent) < $Voltage * 100 || \
- $CheckHealthLast->$Name * 100 > $Voltage * (100 + $CheckHealthVoltagePercent)) do={
- $SendNotification ([ $SymbolForNotification "high-voltage-sign" ] . "Health warning: " . $Name) \
- ("The " . $Name . " on " . $Identity . " jumped more than " . $CheckHealthVoltagePercent . "%.\n\n" . \
- "old value: " . [ $FormatVoltage ($CheckHealthLast->$Name) ] . "\n" . \
- "new value: " . [ $FormatVoltage $Voltage ]);
- }
- }
-}
-
-:foreach Name,PSU in=$CheckHealthCurrent do={
- :if ($Name ~ "psu.*-state" && \
- [ :typeof ($CheckHealthLast->$Name) ] = "str" && \
- [ :typeof $PSU ] = "str") do={
- :if ($CheckHealthLast->$Name = "ok" && \
- $PSU != "ok") do={
- $SendNotification ([ $SymbolForNotification "cross-mark" ] . "Health warning: " . $Name) \
- ("The power supply unit '" . $Name . "' on " . $Identity . " failed!");
- }
- :if ($CheckHealthLast->$Name != "ok" && \
- $PSU = "ok") do={
- $SendNotification ([ $SymbolForNotification "white-heavy-check-mark" ] . "Health recovery: " . $Name) \
- ("The power supply unit '" . $Name . "' on " . $Identity . " recovered!");
- }
- }
-}
-
-:foreach Name,Temperature in=$CheckHealthCurrent do={
- :if ($Name ~ "temperature" && \
- [ :typeof $Temperature ] = "num") do={
- :if ([ :typeof ($CheckHealthTemperature->$Name) ] != "num" ) do={
- $LogPrintExit2 info $0 ("No threshold given for " . $Name . ", assuming 50C.") false;
- :set ($CheckHealthTemperature->$Name) 50;
- }
- :local Validate [ / system health get $Name ];
- :while ($Temperature != $Validate) do={
- :set Temperature $Validate;
- :set Validate [ / system health get $Name ];
- }
- :if ($Temperature > $CheckHealthTemperature->$Name && \
- $CheckHealthTemperatureNotified->$Name != true) do={
- $SendNotification ([ $SymbolForNotification "fire" ] . "Health warning: " . $Name) \
- ("The " . $Name . " on " . $Identity . " is above threshold: " . \
- $Temperature . "\C2\B0" . "C");
- :set ($CheckHealthTemperatureNotified->$Name) true;
- }
- :if ($Temperature <= ($CheckHealthTemperature->$Name - $CheckHealthTemperatureDeviation) && \
- $CheckHealthTemperatureNotified->$Name = true) do={
- $SendNotification ([ $SymbolForNotification "white-heavy-check-mark" ] . "Health recovery: " . $Name) \
- ("The " . $Name . " on " . $Identity . " dropped below threshold: " . \
- $Temperature . "\C2\B0" . "C");
- :set ($CheckHealthTemperatureNotified->$Name) false;
- }
- }
-}
-
-:set CheckHealthLast $CheckHealthCurrent;
diff --git a/check-health.rsc b/check-health.rsc
new file mode 100644
index 0000000..2a97ad6
--- /dev/null
+++ b/check-health.rsc
@@ -0,0 +1,178 @@
+#!rsc by RouterOS
+# RouterOS script: check-health
+# Copyright (c) 2019-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# check for RouterOS health state
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-health.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global CheckHealthCPUUtilization;
+ :global CheckHealthCPUUtilizationNotified;
+ :global CheckHealthLast;
+ :global CheckHealthRAMUtilizationNotified;
+ :global CheckHealthTemperature;
+ :global CheckHealthTemperatureDeviation;
+ :global CheckHealthTemperatureNotified;
+ :global CheckHealthVoltageLow;
+ :global CheckHealthVoltagePercent;
+ :global Identity;
+
+ :global FormatLine;
+ :global HumanReadableNum;
+ :global IfThenElse;
+ :global LogPrint;
+ :global ScriptLock;
+ :global SendNotification2;
+ :global SymbolForNotification;
+
+ :local TempToNum do={
+ :global CharacterReplace;
+ :local T [ :toarray [ $CharacterReplace $1 "." "," ] ];
+ :return ($T->0 * 10 + $T->1);
+ }
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :local Resource [ /system/resource/get ];
+
+ :set CheckHealthCPUUtilization (($CheckHealthCPUUtilization * 4 + ($Resource->"cpu-load") * 10) / 5);
+ :if ($CheckHealthCPUUtilization > 750 && $CheckHealthCPUUtilizationNotified != true) do={
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "abacus,chart-increasing" ] . "Health warning: CPU utilization"); \
+ message=("The average CPU utilization on " . $Identity . " is at " . ($CheckHealthCPUUtilization / 10) . "%!") });
+ :set CheckHealthCPUUtilizationNotified true;
+ }
+ :if ($CheckHealthCPUUtilization < 650 && $CheckHealthCPUUtilizationNotified = true) do={
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "abacus,chart-decreasing" ] . "Health recovery: CPU utilization"); \
+ message=("The average CPU utilization on " . $Identity . " decreased to " . ($CheckHealthCPUUtilization / 10) . "%.") });
+ :set CheckHealthCPUUtilizationNotified false;
+ }
+
+ :local CheckHealthRAMUtilization (($Resource->"total-memory" - $Resource->"free-memory") * 100 / $Resource->"total-memory");
+ :if ($CheckHealthRAMUtilization >=80 && $CheckHealthRAMUtilizationNotified != true) do={
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "card-file-box,chart-increasing" ] . "Health warning: RAM utilization"); \
+ message=("The RAM utilization on " . $Identity . " is at " . $CheckHealthRAMUtilization . "%!\n\n" . \
+ [ $FormatLine "total" ([ $HumanReadableNum ($Resource->"total-memory") 1024 ] . "iB") 8 ] . "\n" . \
+ [ $FormatLine "used" ([ $HumanReadableNum ($Resource->"total-memory" - $Resource->"free-memory") 1024 ] . "iB") 8 ] . "\n" . \
+ [ $FormatLine "free" ([ $HumanReadableNum ($Resource->"free-memory") 1024 ] . "iB") 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;
+ }
+
+ :if ([ :len [ /system/health/find ] ] = 0) do={
+ $LogPrint debug $ScriptName ("Your device does not provide any health values.");
+ :error true;
+ }
+
+ :if ([ :typeof $CheckHealthLast ] != "array") do={
+ :set CheckHealthLast ({});
+ }
+ :if ([ :typeof $CheckHealthTemperatureNotified ] != "array") do={
+ :set CheckHealthTemperatureNotified ({});
+ }
+
+
+ :foreach Voltage in=[ /system/health/find where type="V" ] do={
+ :local Name [ /system/health/get $Voltage name ];
+ :local Value [ /system/health/get $Voltage value ];
+
+ :if ([ :typeof ($CheckHealthLast->$Name) ] != "nothing") do={
+ :local NumCurr [ $TempToNum $Value ];
+ :local NumLast [ $TempToNum ($CheckHealthLast->$Name) ];
+
+ :if ($NumLast * (100 + $CheckHealthVoltagePercent) < $NumCurr * 100 || \
+ $NumLast * 100 > $NumCurr * (100 + $CheckHealthVoltagePercent)) do={
+ $SendNotification2 ({ origin=$ScriptName; \
+ 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=$ScriptName; \
+ 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=$ScriptName; \
+ subject=([ $SymbolForNotification "high-voltage-sign,chart-increasing" ] . "Health recovery: Low " . $Name); \
+ message=("The " . $Name . " on " . $Identity . " recovered to " . $Value . " V above hard limit.") });
+ }
+ }
+ }
+ :set ($CheckHealthLast->$Name) $Value;
+ }
+
+ :foreach PSU in=[ /system/health/find where name~"^psu.*-state\$" ] do={
+ :local Name [ /system/health/get $PSU name ];
+ :local Value [ /system/health/get $PSU value ];
+
+ :if ([ :typeof ($CheckHealthLast->$Name) ] != "nothing") do={
+ :if ($CheckHealthLast->$Name = "ok" && \
+ $Value != "ok") do={
+ $SendNotification2 ({ origin=$ScriptName; \
+ 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=$ScriptName; \
+ subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Health recovery: " . $Name); \
+ message=("The power supply unit '" . $Name . "' on " . $Identity . " recovered!") });
+ }
+ }
+ :set ($CheckHealthLast->$Name) $Value;
+ }
+
+ :foreach Temperature in=[ /system/health/find where type="C" ] do={
+ :local Name [ /system/health/get $Temperature name ];
+ :local Value [ /system/health/get $Temperature value ];
+
+ :if ([ :typeof ($CheckHealthLast->$Name) ] != "nothing") do={
+ :if ([ :typeof ($CheckHealthTemperature->$Name) ] != "num" ) do={
+ $LogPrint info $ScriptName ("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=$ScriptName; \
+ 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=$ScriptName; \
+ 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;
+ }
+} on-error={ }
diff --git a/check-lte-firmware-upgrade b/check-lte-firmware-upgrade
deleted file mode 100644
index f55ec5f..0000000
--- a/check-lte-firmware-upgrade
+++ /dev/null
@@ -1,45 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: check-lte-firmware-upgrade
-# Copyright (c) 2018-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# check for LTE firmware upgrade, send notification
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-lte-firmware-upgrade.md
-
-:local 0 "check-lte-firmware-upgrade";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global Identity;
-:global SentLteFirmwareUpgradeNotification;
-
-:global CharacterReplace;
-:global LogPrintExit2;
-:global SendNotification;
-:global SymbolForNotification;
-
-:foreach Interface in=[ / interface lte find ] do={
- :local IntName [ / interface lte get $Interface name ];
- :do {
- :local Firmware [ / interface lte firmware-upgrade $Interface once as-value ];
-
- :if ($SentLteFirmwareUpgradeNotification = ($Firmware->"latest")) do={
- $LogPrintExit2 debug $0 ("Already sent the LTE firmware upgrade notification for version " . \
- ($Firmware->"latest") . ".") false;
- } else={
- :if (($Firmware->"installed") != ($Firmware->"latest")) do={
- :local Info [ / interface lte info $Interface once as-value ];
- $SendNotification ([ $SymbolForNotification "sparkles" ] . "LTE firmware upgrade") \
- ("A new firmware version " . ($Firmware->"latest") . " is available for " . \
- "LTE interface " . $IntName . " on " . $Identity . ".\n\n" . \
- "Interface: " . [ $CharacterReplace ($Info->"manufacturer" . " " . $Info->"model") ("\"") "" ] . "\n" . \
- "Installed: " . ($Firmware->"installed") . "\n" . \
- "Available: " . ($Firmware->"latest")) "" "true";
- :set SentLteFirmwareUpgradeNotification ($Firmware->"latest");
- }
- }
- } on-error={
- $LogPrintExit2 debug $0 ("Could not get latest LTE firmware version for interface " . \
- $IntName . ".") false;
- }
-}
diff --git a/check-lte-firmware-upgrade.rsc b/check-lte-firmware-upgrade.rsc
new file mode 100644
index 0000000..e7f06f3
--- /dev/null
+++ b/check-lte-firmware-upgrade.rsc
@@ -0,0 +1,103 @@
+#!rsc by RouterOS
+# RouterOS script: check-lte-firmware-upgrade
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# check for LTE firmware upgrade, send notification
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-lte-firmware-upgrade.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global SentLteFirmwareUpgradeNotification;
+
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :if ([ :typeof $SentLteFirmwareUpgradeNotification ] != "array") do={
+ :global SentLteFirmwareUpgradeNotification ({});
+ }
+
+ :local CheckInterface do={
+ :local ScriptName $1;
+ :local Interface $2;
+
+ :global Identity;
+ :global SentLteFirmwareUpgradeNotification;
+
+ :global FormatLine;
+ :global IfThenElse;
+ :global LogPrint;
+ :global ScriptFromTerminal;
+ :global SendNotification2;
+ :global SymbolForNotification;
+
+ :local IntName [ /interface/lte/get $Interface name ];
+ :local Firmware;
+ :local Info;
+ :do {
+ :set Firmware [ /interface/lte/firmware-upgrade $Interface once as-value ];
+ :set Info [ /interface/lte/monitor $Interface once as-value ];
+ } on-error={
+ $LogPrint debug $ScriptName ("Could not get latest LTE firmware version for interface " . \
+ $IntName . ".");
+ :return false;
+ }
+
+ :if ([ :len ($Firmware->"latest") ] = 0) do={
+ $LogPrint info $ScriptName ("An empty string is not a valid version.");
+ :return false;
+ }
+
+ :if (($Firmware->"installed") = ($Firmware->"latest")) do={
+ :if ([ $ScriptFromTerminal $ScriptName ] = true) do={
+ $LogPrint info $ScriptName ("No firmware upgrade available for LTE interface " . $IntName . ".");
+ }
+ :return true;
+ }
+
+ :if ([ $ScriptFromTerminal $ScriptName ] = true && \
+ [ :len [ /system/script/find where name="unattended-lte-firmware-upgrade" ] ] > 0) do={
+ :put ("Do you want to start unattended lte firmware upgrade for interface " . $IntName . "? [y/N]");
+ :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={
+ /system/script/run unattended-lte-firmware-upgrade;
+ $LogPrint info $ScriptName ("Scheduled lte firmware upgrade for interface " . $IntName . "...");
+ :return true;
+ } else={
+ :put "Canceled...";
+ }
+ }
+
+ :if (($SentLteFirmwareUpgradeNotification->$IntName) = ($Firmware->"latest")) do={
+ $LogPrint debug $ScriptName ("Already sent the LTE firmware upgrade notification for version " . \
+ ($Firmware->"latest") . ".");
+ :return false;
+ }
+
+ $LogPrint info $ScriptName ("A new firmware version " . ($Firmware->"latest") . " is available for " . \
+ "LTE interface " . $IntName . ".");
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "sparkles" ] . "LTE firmware upgrade"); \
+ message=("A new firmware version " . ($Firmware->"latest") . " is available for " . \
+ "LTE interface " . $IntName . " on " . $Identity . ".\n\n" . \
+ [ $IfThenElse ([ :len ($Info->"manufacturer") ] > 0) ([ $FormatLine "Manufacturer" ($Info->"manufacturer") ] . "\n") ] . \
+ [ $IfThenElse ([ :len ($Info->"model") ] > 0) ([ $FormatLine "Model" ($Info->"model") ] . "\n") ] . \
+ [ $IfThenElse ([ :len ($Info->"revision") ] > 0) ([ $FormatLine "Revision" ($Info->"revision") ] . "\n") ] . \
+ "Firmware version:\n" . \
+ [ $FormatLine " Installed" ($Firmware->"installed") ] . "\n" . \
+ [ $FormatLine " Available" ($Firmware->"latest") ]); silent=true });
+ :set ($SentLteFirmwareUpgradeNotification->$IntName) ($Firmware->"latest");
+ }
+
+ :foreach Interface in=[ /interface/lte/find ] do={
+ $CheckInterface $ScriptName $Interface;
+ }
+} on-error={ }
diff --git a/check-routeros-update b/check-routeros-update
deleted file mode 100644
index 9dcef1d..0000000
--- a/check-routeros-update
+++ /dev/null
@@ -1,130 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: check-routeros-update
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# check for RouterOS update, send notification and/or install
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-routeros-update.md
-
-:local 0 "check-routeros-update";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global Identity;
-:global SafeUpdateNeighbor;
-:global SafeUpdatePatch;
-:global SafeUpdateUrl;
-:global SentRouterosUpdateNotification;
-
-:global DeviceInfo;
-:global LogPrintExit2;
-:global ScriptFromTerminal;
-:global SendNotification;
-:global SymbolForNotification;
-:global VersionToNum;
-:global WaitFullyConnected;
-
-:local DoUpdate do={
- :if ([ :len [ / system script find where name="packages-update" ] ] > 0) do={
- / system script run packages-update;
- } else={
- / system package update install without-paging;
- }
- :error "Waiting for system to reboot.";
-}
-
-$WaitFullyConnected;
-
-:if ([ :len [ / system package find where name="wireless" disabled=no ] ] > 0) do={
- :if ([ / interface wireless cap get enabled ] = true && \
- [ / caps-man manager get enabled ] = false) do={
- $LogPrintExit2 error $0 ("System is managed by CAPsMAN, not checking for RouterOS version.") true;
- }
-}
-
-:if ([ :len [ / system scheduler find where name="reboot-for-update" ] ] > 0) do={
- :error "A reboot for update is already scheduled.";
-}
-
-/ system package update check-for-updates without-paging;
-:local Update [ / system package update get ];
-
-:if ([ :len ($Update->"latest-version") ] = 0) do={
- $LogPrintExit2 info $0 ("An empty string is not a valid version.") true;
-}
-
-:local NumInstalled [ $VersionToNum ($Update->"installed-version") ];
-:local NumLatest [ $VersionToNum ($Update->"latest-version") ];
-:local Link ("https://mikrotik.com/download/changelogs/" . $Update->"channel" . "-release-tree");
-
-:if ($NumInstalled < $NumLatest) do={
- :if ($SafeUpdatePatch = true && ($NumInstalled & 0xffff0000) = ($NumLatest & 0xffff0000)) do={
- $LogPrintExit2 info $0 ("Version " . $Update->"latest-version" . " is a patch release, updating...") false;
- $SendNotification ([ $SymbolForNotification "sparkles" ] . "RouterOS update") \
- ("Version " . $Update->"latest-version" . " is a patch update for " . $Update->"channel" . \
- ", updating on " . $Identity . "...") $Link "true";
- $DoUpdate;
- }
-
- :if ($SafeUpdateNeighbor = true && [ :len [ / ip neighbor find where \
- version=($Update->"latest-version" . " (" . $Update->"channel" . ")") ] ] > 0) do={
- $LogPrintExit2 info $0 ("Seen a neighbor running version " . $Update->"latest-version" . ", updating...") false;
- $SendNotification ([ $SymbolForNotification "sparkles" ] . "RouterOS update") \
- ("Seen a neighbor running version " . $Update->"latest-version" . " from " . $Update->"channel" . \
- ", updating on " . $Identity . "...") $Link "true";
- $DoUpdate;
- }
-
- :if ([ :len $SafeUpdateUrl ] > 0) do={
- :local Result;
- :do {
- :set Result [ / tool fetch check-certificate=yes-without-crl \
- ($SafeUpdateUrl . $Update->"channel" . "?installed=" . $Update->"installed-version" . \
- "&latest=" . $Update->"latest-version") output=user as-value ];
- } on-error={
- $LogPrintExit2 warning $0 ("Failed receiving safe version for " . $Update->"channel" . ".") false;
- }
- :if ($Result->"status" = "finished" && $Result->"data" = $Update->"latest-version") do={
- $LogPrintExit2 info $0 ("Version " . $Update->"latest-version" . " is considered safe, updating...") false;
- $SendNotification ([ $SymbolForNotification "sparkles" ] . "RouterOS update") \
- ("Version " . $Update->"latest-version" . " is considered safe for " . $Update->"channel" . \
- ", updating on " . $Identity . "...") $Link "true";
- $DoUpdate;
- }
- }
-
- :if ([ $ScriptFromTerminal $0 ] = true) do={
- :put ("Do you want to install RouterOS version " . $Update->"latest-version" . "? [y/N]");
- :if (([ :terminal inkey timeout=60 ] % 32) = 25) do={
- $DoUpdate;
- } else={
- :put "Canceled...";
- }
- }
-
- :if ($SentRouterosUpdateNotification = $Update->"latest-version") do={
- $LogPrintExit2 info $0 ("Already sent the RouterOS update notification for version " . \
- $Update->"latest-version" . ".") true;
- }
-
- $SendNotification ([ $SymbolForNotification "sparkles" ] . "RouterOS update") \
- ("A new RouterOS version " . ($Update->"latest-version") . \
- " is available for " . $Identity . ".\n\n" . \
- [ $DeviceInfo ]) $Link "true";
- :set SentRouterosUpdateNotification ($Update->"latest-version");
-}
-
-:if ($NumInstalled > $NumLatest) do={
- :if ($SentRouterosUpdateNotification = $Update->"latest-version") do={
- $LogPrintExit2 info $0 ("Already sent the RouterOS downgrade notification for version " . \
- $Update->"latest-version" . ".") true;
- }
-
- $SendNotification ([ $SymbolForNotification "warning-sign" ] . "RouterOS version") \
- ("A different RouterOS version " . ($Update->"latest-version") . \
- " is available for " . $Identity . ", but it is a downgrade.\n\n" . \
- [ $DeviceInfo ]) $Link "true";
- $LogPrintExit2 info $0 ("A different RouterOS version " . ($Update->"latest-version") . \
- " is available for downgrade.") false;
- :set SentRouterosUpdateNotification ($Update->"latest-version");
-}
diff --git a/check-routeros-update.rsc b/check-routeros-update.rsc
new file mode 100644
index 0000000..fde7cb5
--- /dev/null
+++ b/check-routeros-update.rsc
@@ -0,0 +1,166 @@
+#!rsc by RouterOS
+# RouterOS script: check-routeros-update
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# check for RouterOS update, send notification and/or install
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-routeros-update.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global Identity;
+ :global SafeUpdateAll;
+ :global SafeUpdateNeighbor;
+ :global SafeUpdateNeighborIdentity;
+ :global SafeUpdatePatch;
+ :global SafeUpdateUrl;
+ :global SentRouterosUpdateNotification;
+
+ :global DeviceInfo;
+ :global EscapeForRegEx;
+ :global FetchUserAgent;
+ :global LogPrint;
+ :global ScriptFromTerminal;
+ :global ScriptLock;
+ :global SendNotification2;
+ :global SymbolForNotification;
+ :global VersionToNum;
+ :global WaitFullyConnected;
+
+ :local DoUpdate do={
+ :if ([ :len [ /system/script/find where name="packages-update" ] ] > 0) do={
+ /system/script/run packages-update;
+ } else={
+ /system/package/update/install without-paging;
+ }
+ :error "Waiting for system to reboot.";
+ }
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+ $WaitFullyConnected;
+
+ :if ([ :len [ /system/scheduler/find where name="_RebootForUpdate" ] ] > 0) do={
+ :error "A reboot for update is already scheduled.";
+ }
+
+ $LogPrint debug $ScriptName ("Checking for updates...");
+ /system/package/update/check-for-updates without-paging as-value;
+ :local Update [ /system/package/update/get ];
+
+ :if ([ $ScriptFromTerminal $ScriptName ] = true && ($Update->"installed-version") = ($Update->"latest-version")) do={
+ $LogPrint info $ScriptName ("System is already up to date.");
+ :error true;
+ }
+
+ :local NumInstalled [ $VersionToNum ($Update->"installed-version") ];
+ :local NumLatest [ $VersionToNum ($Update->"latest-version") ];
+ :local Link ("https://mikrotik.com/download/changelogs/" . $Update->"channel" . "-release-tree");
+
+ :if ($NumLatest < 117505792) do={
+ $LogPrint info $ScriptName ("The version '" . ($Update->"latest-version") . "' is not a valid version.");
+ :error false;
+ }
+
+ :if ($NumInstalled < $NumLatest) do={
+ :if ($SafeUpdateAll ~ "^YES,? ?PLEASE!?\$") do={
+ $LogPrint info $ScriptName ("Installing ALL versions automatically, including " . \
+ $Update->"latest-version" . "...");
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update: " . $Update->"latest-version"); \
+ message=("Installing ALL versions automatically, including " . $Update->"latest-version" . \
+ "... Updating on " . $Identity . "..."); link=$Link; silent=true });
+ $DoUpdate;
+ }
+
+ :if ($SafeUpdatePatch = true && ($NumInstalled & 0xffff0000) = ($NumLatest & 0xffff0000)) do={
+ $LogPrint info $ScriptName ("Version " . $Update->"latest-version" . " is a patch release, updating...");
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update: " . $Update->"latest-version"); \
+ message=("Version " . $Update->"latest-version" . " is a patch update for " . $Update->"channel" . \
+ ", updating on " . $Identity . "..."); link=$Link; silent=true });
+ $DoUpdate;
+ }
+
+ :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;
+ }
+ }
+
+ :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=({ [ $FetchUserAgent $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;
+ }
+ }
+
+ :if ([ $ScriptFromTerminal $ScriptName ] = true) do={
+ :put ("Do you want to install RouterOS version " . $Update->"latest-version" . "? [y/N]");
+ :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={
+ $DoUpdate;
+ } else={
+ :put "Canceled...";
+ }
+ }
+
+ :if ($SentRouterosUpdateNotification = $Update->"latest-version") do={
+ $LogPrint info $ScriptName ("Already sent the RouterOS update notification for version " . \
+ $Update->"latest-version" . ".");
+ :error true;
+ }
+
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update: " . $Update->"latest-version"); \
+ message=("A new RouterOS version " . ($Update->"latest-version") . \
+ " is available for " . $Identity . ".\n\n" . \
+ [ $DeviceInfo ]); link=$Link; silent=true });
+ :set SentRouterosUpdateNotification ($Update->"latest-version");
+ }
+
+ :if ($NumInstalled > $NumLatest) do={
+ :if ($SentRouterosUpdateNotification = $Update->"latest-version") do={
+ $LogPrint info $ScriptName ("Already sent the RouterOS downgrade notification for version " . \
+ $Update->"latest-version" . ".");
+ :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={ }
diff --git a/cloud-backup b/cloud-backup
deleted file mode 100644
index aa29faa..0000000
--- a/cloud-backup
+++ /dev/null
@@ -1,54 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: cloud-backup
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# upload backup to MikroTik cloud
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/cloud-backup.md
-
-:local 0 "cloud-backup";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global BackupPassword;
-:global BackupRandomDelay;
-:global Identity;
-
-:global DeviceInfo;
-:global LogPrintExit2;
-:global RandomDelay;
-:global ScriptFromTerminal;
-:global SendNotification;
-:global SymbolForNotification;
-:global WaitFullyConnected;
-
-$WaitFullyConnected;
-
-:if ([ $ScriptFromTerminal $0 ] = false && $BackupRandomDelay > 0) do={
- $RandomDelay $BackupRandomDelay;
-}
-
-:do {
- # we are not interested in output, but print is
- # required to fetch information from cloud
- / system backup cloud print as-value;
- :if ([ :len [ / system backup cloud find ] ] > 0) do={
- / system backup cloud upload-file action=create-and-upload \
- password=$BackupPassword replace=[ get ([ find ]->0) name ];
- } else={
- / system backup cloud upload-file action=create-and-upload \
- password=$BackupPassword;
- }
- :local Cloud [ / system backup cloud get ([ find ]->0) ];
-
- $SendNotification ([ $SymbolForNotification "floppy-disk" ] . "Cloud backup") \
- ("Uploaded backup for " . $Identity . " to cloud.\n\n" . \
- [ $DeviceInfo ] . "\n\n" . \
- "Name: " . $Cloud->"name" . "\n" . \
- "Size: " . $Cloud->"size" . " B (" . ($Cloud->"size" / 1024) . " KiB)\n" . \
- "Download key: " . $Cloud->"secret-download-key") "" "true";
-} on-error={
- $SendNotification ([ $SymbolForNotification "warning-sign" ] . "Cloud backup failed") \
- ("Failed uploading backup for " . $Identity . " to cloud!\n\n" . [ $DeviceInfo ]);
- $LogPrintExit2 error $0 ("Failed uploading backup for " . $Identity . " to cloud!") true;
-}
diff --git a/collect-wireless-mac.capsman b/collect-wireless-mac.capsman
deleted file mode 100644
index ca43dc5..0000000
--- a/collect-wireless-mac.capsman
+++ /dev/null
@@ -1,74 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: collect-wireless-mac.capsman
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# collect wireless mac adresses in access list
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/collect-wireless-mac.md
-#
-# !! Do not edit this file, it is generated from template!
-
-:local 0 "collect-wireless-mac.capsman";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global Identity;
-
-:global GetMacVendor;
-:global LogPrintExit2;
-:global ScriptLock;
-:global SendNotification;
-:global SymbolForNotification;
-
-$ScriptLock $0;
-
-:if ([ :len [ / caps-man access-list find where comment="--- collected above ---" disabled ] ] = 0) do={
- / caps-man access-list add comment="--- collected above ---" disabled=yes;
- $LogPrintExit2 warning $0 ("Added disabled access-list entry with comment '--- collected above ---'.") false;
-}
-:local PlaceBefore ([ / caps-man access-list find where comment="--- collected above ---" disabled ]->0);
-
-:foreach RegTbl in=[ / caps-man registration-table find ] do={
- :local Mac [ / caps-man registration-table get $RegTbl mac-address ];
- :local AccessList ([ / caps-man access-list find where mac-address=$Mac ]->0);
- :if ([ :len $AccessList ] = 0) do={
- :local Address "no dhcp lease";
- :local DnsName "no dhcp lease";
- :local HostName "no dhcp lease";
- :local Lease ([ / ip dhcp-server lease find where mac-address=$Mac dynamic=yes status=bound ]->0);
- :if ([ :len $Lease ] > 0) do={
- :set Address [ / ip dhcp-server lease get $Lease address ];
- :set HostName [ / ip dhcp-server lease get $Lease host-name ];
- :if ([ :len $HostName ] = 0) do={
- :set HostName "no hostname";
- }
- :set DnsName [ / ip dns static get ([ find where address=$Address ]->0) name ];
- :if ([ :len $DnsName ] = 0) do={
- :set DnsName "no dns name";
- }
- }
- :local RegEntry [ / caps-man registration-table find where mac-address=$Mac ];
- :local Interface [ / caps-man registration-table get $RegEntry interface ];
- :local Ssid [ / caps-man registration-table get $RegEntry ssid ];
- :local DateTime ([ / system clock get date ] . " " . [ / system clock get time ]);
- :local Vendor [ $GetMacVendor $Mac ];
- :local Message ("unknown MAC address " . $Mac . " (" . $Vendor . ", " . $HostName . ") " . \
- "first seen on " . $DateTime . " connected to SSID " . $Ssid . ", interface " . $Interface);
- $LogPrintExit2 info $0 $Message false;
- / caps-man access-list add place-before=$PlaceBefore comment=$Message mac-address=$Mac disabled=yes;
- $SendNotification ([ $SymbolForNotification "mobile-phone" ] . $Mac . " connected to " . $Ssid) \
- ("A device with unknown MAC address connected to " . $Ssid . " on " . $Identity . ".\n\n" . \
- "Controller: " . $Identity . "\n" . \
- "Interface: " . $Interface . "\n" . \
- "SSID: " . $Ssid . "\n" . \
- "MAC: " . $Mac . "\n" . \
- "Vendor: " . $Vendor . "\n" . \
- "Hostname: " . $HostName . "\n" . \
- "Address: " . $Address . "\n" . \
- "DNS name: " . $DnsName . "\n" . \
- "Date: " . $DateTime);
- } else={
- $LogPrintExit2 debug $0 ("MAC address " . $Mac . " already known: " . \
- [ / caps-man access-list get $AccessList comment ]) false;
- }
-}
diff --git a/collect-wireless-mac.capsman.rsc b/collect-wireless-mac.capsman.rsc
new file mode 100644
index 0000000..dcb303c
--- /dev/null
+++ b/collect-wireless-mac.capsman.rsc
@@ -0,0 +1,96 @@
+#!rsc by RouterOS
+# RouterOS script: collect-wireless-mac.capsman
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: lease-script, order=40
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+: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={
+ :error false;
+ }
+
+ :if ([ :len [ /caps-man/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={
+ /caps-man/access-list/add comment="--- collected above ---" disabled=yes;
+ $LogPrint warning $ScriptName ("Added disabled access-list entry with comment '--- collected above ---'.");
+ }
+ :local PlaceBefore ([ /caps-man/access-list/find where comment="--- collected above ---" disabled ]->0);
+
+ :foreach Reg in=[ /caps-man/registration-table/find ] do={
+ :local RegVal;
+ :do {
+ :set RegVal [ /caps-man/registration-table/get $Reg ];
+ } on-error={
+ $LogPrint debug $ScriptName ("Device already gone... Ignoring.");
+ }
+
+ :if ([ :len ($RegVal->"mac-address") ] > 0) do={
+ :local AccessList ([ /caps-man/access-list/find where mac-address=($RegVal->"mac-address") ]->0);
+ :if ([ :len $AccessList ] > 0) do={
+ $LogPrint debug $ScriptName ("MAC address " . $RegVal->"mac-address" . " already known: " . \
+ [ /caps-man/access-list/get $AccessList comment ]);
+ }
+
+ :if ([ :len $AccessList ] = 0) do={
+ :local Address "no dhcp lease";
+ :local DnsName "no dhcp lease";
+ :local HostName "no dhcp lease";
+ :local Lease ([ /ip/dhcp-server/lease/find where active-mac-address=($RegVal->"mac-address") dynamic=yes status=bound ]->0);
+ :if ([ :len $Lease ] > 0) do={
+ :set Address [ /ip/dhcp-server/lease/get $Lease active-address ];
+ :set HostName [ $EitherOr [ /ip/dhcp-server/lease/get $Lease host-name ] "no hostname" ];
+ :set DnsName "no dns name";
+ :local DnsRec ([ /ip/dns/static/find where address=$Address ]->0);
+ :if ([ :len $DnsRec ] > 0) do={
+ :set DnsName ({ [ /ip/dns/static/get $DnsRec name ] });
+ :foreach CName in=[ /ip/dns/static/find where type=CNAME cname=($DnsName->0) ] do={
+ :set DnsName ($DnsName, [ /ip/dns/static/get $CName name ]);
+ }
+ }
+ }
+ :local DateTime ([ /system/clock/get date ] . " " . [ /system/clock/get time ]);
+ :local Vendor [ $GetMacVendor ($RegVal->"mac-address") ];
+ :local Message ("MAC address " . $RegVal->"mac-address" . " (" . $Vendor . ", " . $HostName . ") " . \
+ "first seen on " . $DateTime . " connected to SSID " . $RegVal->"ssid" . ", interface " . $RegVal->"interface");
+ $LogPrint info $ScriptName $Message;
+ /caps-man/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes;
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "mobile-phone" ] . $RegVal->"mac-address" . " connected to " . $RegVal->"ssid"); \
+ message=("A device with unknown MAC address connected to " . $RegVal->"ssid" . " on " . $Identity . ".\n\n" . \
+ [ $FormatLine "Controller" $Identity ] . "\n" . \
+ [ $FormatLine "Interface" ($RegVal->"interface") ] . "\n" . \
+ [ $FormatLine "SSID" ($RegVal->"ssid") ] . "\n" . \
+ [ $FormatLine "MAC" ($RegVal->"mac-address") ] . "\n" . \
+ [ $FormatLine "Vendor" $Vendor ] . "\n" . \
+ [ $FormatLine "Hostname" $HostName ] . "\n" . \
+ [ $FormatLine "Address" $Address ] . "\n" . \
+ [ $FormatMultiLines "DNS name" $DnsName ] . "\n" . \
+ [ $FormatLine "Date" $DateTime ]) });
+ }
+ } else={
+ $LogPrint debug $ScriptName ("No mac address available... Ignoring.");
+ }
+ }
+} on-error={ }
diff --git a/collect-wireless-mac.local b/collect-wireless-mac.local
deleted file mode 100644
index 7489c4c..0000000
--- a/collect-wireless-mac.local
+++ /dev/null
@@ -1,74 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: collect-wireless-mac.local
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# collect wireless mac adresses in access list
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/collect-wireless-mac.md
-#
-# !! Do not edit this file, it is generated from template!
-
-:local 0 "collect-wireless-mac.local";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global Identity;
-
-:global GetMacVendor;
-:global LogPrintExit2;
-:global ScriptLock;
-:global SendNotification;
-:global SymbolForNotification;
-
-$ScriptLock $0;
-
-:if ([ :len [ / interface wireless access-list find where comment="--- collected above ---" disabled ] ] = 0) do={
- / interface wireless access-list add comment="--- collected above ---" disabled=yes;
- $LogPrintExit2 warning $0 ("Added disabled access-list entry with comment '--- collected above ---'.") false;
-}
-:local PlaceBefore ([ / interface wireless access-list find where comment="--- collected above ---" disabled ]->0);
-
-:foreach RegTbl in=[ / interface wireless registration-table find ] do={
- :local Mac [ / interface wireless registration-table get $RegTbl mac-address ];
- :local AccessList ([ / interface wireless access-list find where mac-address=$Mac ]->0);
- :if ([ :len $AccessList ] = 0) do={
- :local Address "no dhcp lease";
- :local DnsName "no dhcp lease";
- :local HostName "no dhcp lease";
- :local Lease ([ / ip dhcp-server lease find where mac-address=$Mac dynamic=yes status=bound ]->0);
- :if ([ :len $Lease ] > 0) do={
- :set Address [ / ip dhcp-server lease get $Lease address ];
- :set HostName [ / ip dhcp-server lease get $Lease host-name ];
- :if ([ :len $HostName ] = 0) do={
- :set HostName "no hostname";
- }
- :set DnsName [ / ip dns static get ([ find where address=$Address ]->0) name ];
- :if ([ :len $DnsName ] = 0) do={
- :set DnsName "no dns name";
- }
- }
- :local RegEntry [ / interface wireless registration-table find where mac-address=$Mac ];
- :local Interface [ / interface wireless registration-table get $RegEntry interface ];
- :local Ssid [ / interface wireless get [ find where name=$Interface ] ssid ];
- :local DateTime ([ / system clock get date ] . " " . [ / system clock get time ]);
- :local Vendor [ $GetMacVendor $Mac ];
- :local Message ("unknown MAC address " . $Mac . " (" . $Vendor . ", " . $HostName . ") " . \
- "first seen on " . $DateTime . " connected to SSID " . $Ssid . ", interface " . $Interface);
- $LogPrintExit2 info $0 $Message false;
- / interface wireless access-list add place-before=$PlaceBefore comment=$Message mac-address=$Mac disabled=yes;
- $SendNotification ([ $SymbolForNotification "mobile-phone" ] . $Mac . " connected to " . $Ssid) \
- ("A device with unknown MAC address connected to " . $Ssid . " on " . $Identity . ".\n\n" . \
- "Controller: " . $Identity . "\n" . \
- "Interface: " . $Interface . "\n" . \
- "SSID: " . $Ssid . "\n" . \
- "MAC: " . $Mac . "\n" . \
- "Vendor: " . $Vendor . "\n" . \
- "Hostname: " . $HostName . "\n" . \
- "Address: " . $Address . "\n" . \
- "DNS name: " . $DnsName . "\n" . \
- "Date: " . $DateTime);
- } else={
- $LogPrintExit2 debug $0 ("MAC address " . $Mac . " already known: " . \
- [ / interface wireless access-list get $AccessList comment ]) false;
- }
-}
diff --git a/collect-wireless-mac.local.rsc b/collect-wireless-mac.local.rsc
new file mode 100644
index 0000000..7c1122c
--- /dev/null
+++ b/collect-wireless-mac.local.rsc
@@ -0,0 +1,97 @@
+#!rsc by RouterOS
+# RouterOS script: collect-wireless-mac.local
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: lease-script, order=40
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+: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={
+ :error false;
+ }
+
+ :if ([ :len [ /interface/wireless/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={
+ /interface/wireless/access-list/add comment="--- collected above ---" disabled=yes;
+ $LogPrint warning $ScriptName ("Added disabled access-list entry with comment '--- collected above ---'.");
+ }
+ :local PlaceBefore ([ /interface/wireless/access-list/find where comment="--- collected above ---" disabled ]->0);
+
+ :foreach Reg in=[ /interface/wireless/registration-table/find where ap=no ] do={
+ :local RegVal;
+ :do {
+ :set RegVal [ /interface/wireless/registration-table/get $Reg ];
+ } on-error={
+ $LogPrint debug $ScriptName ("Device already gone... Ignoring.");
+ }
+
+ :if ([ :len ($RegVal->"mac-address") ] > 0) do={
+ :local AccessList ([ /interface/wireless/access-list/find where mac-address=($RegVal->"mac-address") ]->0);
+ :if ([ :len $AccessList ] > 0) do={
+ $LogPrint debug $ScriptName ("MAC address " . $RegVal->"mac-address" . " already known: " . \
+ [ /interface/wireless/access-list/get $AccessList comment ]);
+ }
+
+ :if ([ :len $AccessList ] = 0) do={
+ :local Address "no dhcp lease";
+ :local DnsName "no dhcp lease";
+ :local HostName "no dhcp lease";
+ :local Lease ([ /ip/dhcp-server/lease/find where active-mac-address=($RegVal->"mac-address") dynamic=yes status=bound ]->0);
+ :if ([ :len $Lease ] > 0) do={
+ :set Address [ /ip/dhcp-server/lease/get $Lease active-address ];
+ :set HostName [ $EitherOr [ /ip/dhcp-server/lease/get $Lease host-name ] "no hostname" ];
+ :set DnsName "no dns name";
+ :local DnsRec ([ /ip/dns/static/find where address=$Address ]->0);
+ :if ([ :len $DnsRec ] > 0) do={
+ :set DnsName ({ [ /ip/dns/static/get $DnsRec name ] });
+ :foreach CName in=[ /ip/dns/static/find where type=CNAME cname=($DnsName->0) ] do={
+ :set DnsName ($DnsName, [ /ip/dns/static/get $CName name ]);
+ }
+ }
+ }
+ :set ($RegVal->"ssid") [ /interface/wireless/get [ find where name=($RegVal->"interface") ] ssid ];
+ :local DateTime ([ /system/clock/get date ] . " " . [ /system/clock/get time ]);
+ :local Vendor [ $GetMacVendor ($RegVal->"mac-address") ];
+ :local Message ("MAC address " . $RegVal->"mac-address" . " (" . $Vendor . ", " . $HostName . ") " . \
+ "first seen on " . $DateTime . " connected to SSID " . $RegVal->"ssid" . ", interface " . $RegVal->"interface");
+ $LogPrint info $ScriptName $Message;
+ /interface/wireless/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes;
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "mobile-phone" ] . $RegVal->"mac-address" . " connected to " . $RegVal->"ssid"); \
+ message=("A device with unknown MAC address connected to " . $RegVal->"ssid" . " on " . $Identity . ".\n\n" . \
+ [ $FormatLine "Controller" $Identity ] . "\n" . \
+ [ $FormatLine "Interface" ($RegVal->"interface") ] . "\n" . \
+ [ $FormatLine "SSID" ($RegVal->"ssid") ] . "\n" . \
+ [ $FormatLine "MAC" ($RegVal->"mac-address") ] . "\n" . \
+ [ $FormatLine "Vendor" $Vendor ] . "\n" . \
+ [ $FormatLine "Hostname" $HostName ] . "\n" . \
+ [ $FormatLine "Address" $Address ] . "\n" . \
+ [ $FormatMultiLines "DNS name" $DnsName ] . "\n" . \
+ [ $FormatLine "Date" $DateTime ]) });
+ }
+ } else={
+ $LogPrint debug $ScriptName ("No mac address available... Ignoring.");
+ }
+ }
+} on-error={ }
diff --git a/collect-wireless-mac.template b/collect-wireless-mac.template
deleted file mode 100644
index b6c4efa..0000000
--- a/collect-wireless-mac.template
+++ /dev/null
@@ -1,76 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: collect-wireless-mac%TEMPL%
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# collect wireless mac adresses in access list
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/collect-wireless-mac.md
-#
-# !! This is just a template! Replace '%PATH%' with 'caps-man'
-# !! or 'interface wireless'!
-
-:local 0 "collect-wireless-mac%TEMPL%";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global Identity;
-
-:global GetMacVendor;
-:global LogPrintExit2;
-:global ScriptLock;
-:global SendNotification;
-:global SymbolForNotification;
-
-$ScriptLock $0;
-
-:if ([ :len [ / %PATH% access-list find where comment="--- collected above ---" disabled ] ] = 0) do={
- / %PATH% access-list add comment="--- collected above ---" disabled=yes;
- $LogPrintExit2 warning $0 ("Added disabled access-list entry with comment '--- collected above ---'.") false;
-}
-:local PlaceBefore ([ / %PATH% access-list find where comment="--- collected above ---" disabled ]->0);
-
-:foreach RegTbl in=[ / %PATH% registration-table find ] do={
- :local Mac [ / %PATH% registration-table get $RegTbl mac-address ];
- :local AccessList ([ / %PATH% access-list find where mac-address=$Mac ]->0);
- :if ([ :len $AccessList ] = 0) do={
- :local Address "no dhcp lease";
- :local DnsName "no dhcp lease";
- :local HostName "no dhcp lease";
- :local Lease ([ / ip dhcp-server lease find where mac-address=$Mac dynamic=yes status=bound ]->0);
- :if ([ :len $Lease ] > 0) do={
- :set Address [ / ip dhcp-server lease get $Lease address ];
- :set HostName [ / ip dhcp-server lease get $Lease host-name ];
- :if ([ :len $HostName ] = 0) do={
- :set HostName "no hostname";
- }
- :set DnsName [ / ip dns static get ([ find where address=$Address ]->0) name ];
- :if ([ :len $DnsName ] = 0) do={
- :set DnsName "no dns name";
- }
- }
- :local RegEntry [ / %PATH% registration-table find where mac-address=$Mac ];
- :local Interface [ / %PATH% registration-table get $RegEntry interface ];
- :local Ssid [ / caps-man registration-table get $RegEntry ssid ];
- :local Ssid [ / interface wireless get [ find where name=$Interface ] ssid ];
- :local DateTime ([ / system clock get date ] . " " . [ / system clock get time ]);
- :local Vendor [ $GetMacVendor $Mac ];
- :local Message ("unknown MAC address " . $Mac . " (" . $Vendor . ", " . $HostName . ") " . \
- "first seen on " . $DateTime . " connected to SSID " . $Ssid . ", interface " . $Interface);
- $LogPrintExit2 info $0 $Message false;
- / %PATH% access-list add place-before=$PlaceBefore comment=$Message mac-address=$Mac disabled=yes;
- $SendNotification ([ $SymbolForNotification "mobile-phone" ] . $Mac . " connected to " . $Ssid) \
- ("A device with unknown MAC address connected to " . $Ssid . " on " . $Identity . ".\n\n" . \
- "Controller: " . $Identity . "\n" . \
- "Interface: " . $Interface . "\n" . \
- "SSID: " . $Ssid . "\n" . \
- "MAC: " . $Mac . "\n" . \
- "Vendor: " . $Vendor . "\n" . \
- "Hostname: " . $HostName . "\n" . \
- "Address: " . $Address . "\n" . \
- "DNS name: " . $DnsName . "\n" . \
- "Date: " . $DateTime);
- } else={
- $LogPrintExit2 debug $0 ("MAC address " . $Mac . " already known: " . \
- [ / %PATH% access-list get $AccessList comment ]) false;
- }
-}
diff --git a/collect-wireless-mac.template.rsc b/collect-wireless-mac.template.rsc
new file mode 100644
index 0000000..b8c5ff8
--- /dev/null
+++ b/collect-wireless-mac.template.rsc
@@ -0,0 +1,114 @@
+#!rsc by RouterOS
+# RouterOS script: collect-wireless-mac%TEMPL%
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: lease-script, order=40
+# requires RouterOS, version=7.12
+#
+# collect wireless mac adresses in access list
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/collect-wireless-mac.md
+#
+# !! This is just a template to generate the real script!
+# !! Pattern '%TEMPL%' is replaced, paths are filtered.
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+: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={
+ :error false;
+ }
+
+ :if ([ :len [ /caps-man/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={
+ :if ([ :len [ /interface/wifi/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={
+ :if ([ :len [ /interface/wireless/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={
+ /caps-man/access-list/add comment="--- collected above ---" disabled=yes;
+ /interface/wifi/access-list/add comment="--- collected above ---" disabled=yes;
+ /interface/wireless/access-list/add comment="--- collected above ---" disabled=yes;
+ $LogPrint warning $ScriptName ("Added disabled access-list entry with comment '--- collected above ---'.");
+ }
+ :local PlaceBefore ([ /caps-man/access-list/find where comment="--- collected above ---" disabled ]->0);
+ :local PlaceBefore ([ /interface/wifi/access-list/find where comment="--- collected above ---" disabled ]->0);
+ :local PlaceBefore ([ /interface/wireless/access-list/find where comment="--- collected above ---" disabled ]->0);
+
+ :foreach Reg in=[ /caps-man/registration-table/find ] do={
+ :foreach Reg in=[ /interface/wifi/registration-table/find ] do={
+ :foreach Reg in=[ /interface/wireless/registration-table/find where ap=no ] do={
+ :local RegVal;
+ :do {
+ :set RegVal [ /caps-man/registration-table/get $Reg ];
+ :set RegVal [ /interface/wifi/registration-table/get $Reg ];
+ :set RegVal [ /interface/wireless/registration-table/get $Reg ];
+ } on-error={
+ $LogPrint debug $ScriptName ("Device already gone... Ignoring.");
+ }
+
+ :if ([ :len ($RegVal->"mac-address") ] > 0) do={
+ :local AccessList ([ /caps-man/access-list/find where mac-address=($RegVal->"mac-address") ]->0);
+ :local AccessList ([ /interface/wifi/access-list/find where mac-address=($RegVal->"mac-address") ]->0);
+ :local AccessList ([ /interface/wireless/access-list/find where mac-address=($RegVal->"mac-address") ]->0);
+ :if ([ :len $AccessList ] > 0) do={
+ $LogPrint debug $ScriptName ("MAC address " . $RegVal->"mac-address" . " already known: " . \
+ [ /caps-man/access-list/get $AccessList comment ]);
+ [ /interface/wifi/access-list/get $AccessList comment ]);
+ [ /interface/wireless/access-list/get $AccessList comment ]);
+ }
+
+ :if ([ :len $AccessList ] = 0) do={
+ :local Address "no dhcp lease";
+ :local DnsName "no dhcp lease";
+ :local HostName "no dhcp lease";
+ :local Lease ([ /ip/dhcp-server/lease/find where active-mac-address=($RegVal->"mac-address") dynamic=yes status=bound ]->0);
+ :if ([ :len $Lease ] > 0) do={
+ :set Address [ /ip/dhcp-server/lease/get $Lease active-address ];
+ :set HostName [ $EitherOr [ /ip/dhcp-server/lease/get $Lease host-name ] "no hostname" ];
+ :set DnsName "no dns name";
+ :local DnsRec ([ /ip/dns/static/find where address=$Address ]->0);
+ :if ([ :len $DnsRec ] > 0) do={
+ :set DnsName ({ [ /ip/dns/static/get $DnsRec name ] });
+ :foreach CName in=[ /ip/dns/static/find where type=CNAME cname=($DnsName->0) ] do={
+ :set DnsName ($DnsName, [ /ip/dns/static/get $CName name ]);
+ }
+ }
+ }
+ :set ($RegVal->"ssid") [ /interface/wireless/get [ find where name=($RegVal->"interface") ] ssid ];
+ :local DateTime ([ /system/clock/get date ] . " " . [ /system/clock/get time ]);
+ :local Vendor [ $GetMacVendor ($RegVal->"mac-address") ];
+ :local Message ("MAC address " . $RegVal->"mac-address" . " (" . $Vendor . ", " . $HostName . ") " . \
+ "first seen on " . $DateTime . " connected to SSID " . $RegVal->"ssid" . ", interface " . $RegVal->"interface");
+ $LogPrint info $ScriptName $Message;
+ /caps-man/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes;
+ /interface/wifi/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes;
+ /interface/wireless/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes;
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "mobile-phone" ] . $RegVal->"mac-address" . " connected to " . $RegVal->"ssid"); \
+ message=("A device with unknown MAC address connected to " . $RegVal->"ssid" . " on " . $Identity . ".\n\n" . \
+ [ $FormatLine "Controller" $Identity ] . "\n" . \
+ [ $FormatLine "Interface" ($RegVal->"interface") ] . "\n" . \
+ [ $FormatLine "SSID" ($RegVal->"ssid") ] . "\n" . \
+ [ $FormatLine "MAC" ($RegVal->"mac-address") ] . "\n" . \
+ [ $FormatLine "Vendor" $Vendor ] . "\n" . \
+ [ $FormatLine "Hostname" $HostName ] . "\n" . \
+ [ $FormatLine "Address" $Address ] . "\n" . \
+ [ $FormatMultiLines "DNS name" $DnsName ] . "\n" . \
+ [ $FormatLine "Date" $DateTime ]) });
+ }
+ } else={
+ $LogPrint debug $ScriptName ("No mac address available... Ignoring.");
+ }
+ }
+} on-error={ }
diff --git a/collect-wireless-mac.wifi.rsc b/collect-wireless-mac.wifi.rsc
new file mode 100644
index 0000000..b8ad939
--- /dev/null
+++ b/collect-wireless-mac.wifi.rsc
@@ -0,0 +1,96 @@
+#!rsc by RouterOS
+# RouterOS script: collect-wireless-mac.wifi
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: lease-script, order=40
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+: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={
+ :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={ }
diff --git a/contrib/logo-color.d/browser-01.avif b/contrib/logo-color.d/browser-01.avif
new file mode 100644
index 0000000..3dc0a1f
--- /dev/null
+++ b/contrib/logo-color.d/browser-01.avif
Binary files differ
diff --git a/contrib/logo-color.d/browser-02.avif b/contrib/logo-color.d/browser-02.avif
new file mode 100644
index 0000000..1867fbe
--- /dev/null
+++ b/contrib/logo-color.d/browser-02.avif
Binary files differ
diff --git a/contrib/logo-color.d/browser-03.avif b/contrib/logo-color.d/browser-03.avif
new file mode 100644
index 0000000..dc24bbb
--- /dev/null
+++ b/contrib/logo-color.d/browser-03.avif
Binary files differ
diff --git a/contrib/logo-color.d/script.js b/contrib/logo-color.d/script.js
new file mode 100644
index 0000000..82cc204
--- /dev/null
+++ b/contrib/logo-color.d/script.js
@@ -0,0 +1,12 @@
+function invertHex(hex) {
+ return (Number("0x1" + hex) ^ 0xffffff).toString(16).substr(1);
+}
+
+function color() {
+ var svg = document.querySelector(".logo").getSVGDocument();
+ svg.getElementById("dark-1").setAttribute("stop-color", document.getElementById("color1").value);
+ svg.getElementById("dark-2").setAttribute("stop-color", document.getElementById("color2").value);
+ var background = document.getElementById("color3").value;
+ svg.getElementById("background").setAttribute("fill", background);
+ svg.getElementById("hexagon").setAttribute("stroke", "#" + invertHex(background.substring(1)));
+}
diff --git a/contrib/logo-color.d/style.css b/contrib/logo-color.d/style.css
new file mode 100644
index 0000000..eb2ec6a
--- /dev/null
+++ b/contrib/logo-color.d/style.css
@@ -0,0 +1,5 @@
+body {
+ font-family: fira-sans, sans-serif;
+ font-size: 10pt;
+ background-color: transparent;
+}
diff --git a/contrib/logo-color.html b/contrib/logo-color.html
new file mode 100644
index 0000000..17942ce
--- /dev/null
+++ b/contrib/logo-color.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>RouterOS-Scripts Logo Color Changer</title>
+<link rel="stylesheet" type="text/css" href="logo-color.d/style.css">
+<script src="logo-color.d/script.js"></script>
+</head>
+<body>
+
+<h1>RouterOS-Scripts Logo Color Changer</h1>
+
+<p>You want the logo for your own notifications? But you joined the
+<a href="https://t.me/routeros_scripts">Telegram Group</a> and want
+something that differentiates? Color it!</p>
+
+<embed class="logo" src="../logo.svg" width="192" height="192" type="image/svg+xml">
+
+<p>Select the colors here:
+<input id="color1" type="color" value="#222222" onchange="color();">
+<input id="color2" type="color" value="#444444" onchange="color();">
+<input id="color3" type="color" value="#ffffff" onchange="color();"></p>
+
+<p>Then right-click, click "<i>Take Screenshot</i>" and finally select the
+logo and download it.</p>
+
+<p><img src="logo-color.d/browser-01.avif" width=533 height=482 alt="Screenshot Browser 01">
+<img src="logo-color.d/browser-02.avif" width=533 height=482 alt="Screenshot Browser 02">
+<img src="logo-color.d/browser-03.avif" width=533 height=482 alt="Screenshot Browser 03"></p>
+
+<p>(This example is with
+<a href="https://www.mozilla.org/de/firefox/new/">Firefox</a>. The workflow
+for other browsers may differ.)</p>
+
+<p>See how to
+<a href="../../about/doc/mod/notification-telegram.md#set-a-profile-photo">Set
+a profile photo</a> for your Telegram bot.</p>
+
+</body>
+</html>
diff --git a/contrib/notification.d/script.js b/contrib/notification.d/script.js
new file mode 100644
index 0000000..91741fd
--- /dev/null
+++ b/contrib/notification.d/script.js
@@ -0,0 +1,6 @@
+function visible(cb, element) {
+ document.getElementById(element).style.display = cb.checked ? "block" : "none";
+}
+function update(cb, element) {
+ document.getElementById(element).innerHTML = cb.value;
+}
diff --git a/contrib/notification.d/style.css b/contrib/notification.d/style.css
new file mode 100644
index 0000000..648ea23
--- /dev/null
+++ b/contrib/notification.d/style.css
@@ -0,0 +1,36 @@
+body {
+ font-family: fira-sans, sans-serif;
+ font-size: 10pt;
+ background-color: transparent;
+}
+div.notification {
+ position: relative;
+ float: right;
+ width: 600px;
+ border: 3px outset #6c5d53;
+ /* border-radius: 5px; */
+ padding: 10px;
+ background-color: #e6e6e6;
+}
+div.content {
+ padding-left: 60px;
+}
+img.logo {
+ float: left;
+ border-radius: 50%;
+}
+p.heading {
+ margin: 0px;
+ font-weight: bold;
+ text-decoration: underline;
+}
+p.hint {
+ display: none;
+}
+pre {
+ font-family: fira-mono, monospace;
+ white-space: pre-wrap;
+}
+span.link {
+ color: #863600;
+}
diff --git a/contrib/notification.html b/contrib/notification.html
new file mode 100644
index 0000000..7875036
--- /dev/null
+++ b/contrib/notification.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>RouterOS-Scripts Notification Generator</title>
+<link rel="stylesheet" type="text/css" href="notification.d/style.css">
+<script src="notification.d/script.js"></script>
+</head>
+<body>
+
+<h1>RouterOS-Scripts Notification Generator</h1>
+
+<div class="notification">
+ <img src="../logo.svg" alt="logo" class="logo" width=48 height=48>
+ <div class="content">
+ <p id="heading" class="heading">[<span id="hostname">MikroTik</span>] <span id="subject">ℹ️ Subject</span></p>
+ <pre id="message">Message</pre>
+ <p id="link" class="hint">🔗 <span id="link-text" class="link">https://eworm.de/</span></p>
+ <p id="queued" class="hint">⏰ This message was queued since <span id="queued-since">oct/18/2022 18:30:48</span> and may be obsolete.</p>
+ <p id="cut" class="hint">✂️ The message was too long and has been truncated, cut off <span id="cut-percent">13</span>%!</p>
+ </div>
+</div>
+
+<p>Hostname: <input type="text" value="MikroTik" onchange="update(this, 'hostname')"></p>
+<p>Subject: <input type="text" size=50 value="ℹ️ Subject" onchange="update(this, 'subject')"></p>
+<p>Message: <textarea id="w3review" name="w3review" rows="4" cols="50" onchange="update(this, 'message')">Message</textarea></p>
+<p><input type="checkbox" onclick="visible(this, 'link')"> Show link: <input type="text" value="https://eworm.de/" onchange="update(this, 'link-text')"></p>
+<p><input type="checkbox" onclick="visible(this, 'queued')"> Queued since <input type="text" value="oct/18/2022 18:30:48" onchange="update(this, 'queued-since')"></p>
+<p><input type="checkbox" onclick="visible(this, 'cut')"> Cut-off with <input type="number" min=1 max=99 value=13 onchange="update(this, 'cut-percent')"> percent</p>
+
+<p>Then right-click, click "<i>Take Screenshot</i>" and finally select the
+notification and download it.</p>
+
+</body>
+</html>
diff --git a/daily-psk.capsman b/daily-psk.capsman
deleted file mode 100644
index 7d2c2ae..0000000
--- a/daily-psk.capsman
+++ /dev/null
@@ -1,93 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: daily-psk.capsman
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# Michael Gisbers <michael@gisbers.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# update daily PSK (pre shared key)
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/daily-psk.md
-#
-# !! Do not edit this file, it is generated from template!
-
-:local 0 "daily-psk.capsman";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global DailyPskMatchComment;
-:global Identity;
-
-:global LogPrintExit2;
-:global SendNotification;
-:global SymbolForNotification;
-:global UrlEncode;
-:global WaitForFile;
-:global WaitFullyConnected;
-
-$WaitFullyConnected;
-
-# return pseudo-random string for PSK
-:local GeneratePSK do={
- :local Date [ :tostr $1 ];
-
- :global DailyPskSecrets;
-
- :local Months { "jan"; "feb"; "mar"; "apr"; "may"; "jun";
- "jul"; "aug"; "sep"; "oct"; "nov"; "dec" };
-
- :local Month [ :pick $Date 0 3 ];
- :local Day [ :tonum [ :pick $Date 4 6 ] ];
- :local Year [ :pick $Date 7 11 ];
-
- :for MIndex from=0 to=[ :len $Months ] do={
- :if ($Months->$MIndex = $Month) do={
- :set Month ($MIndex + 1);
- }
- }
-
- :local A ((14 - $Month) / 12);
- :local B ($Year - $A);
- :local C ($Month + 12 * $A - 2);
- :local WeekDay (7000 + $Day + $B + ($B / 4) - ($B / 100) + ($B / 400) + ((31 * $C) / 12));
- :set WeekDay ($WeekDay - (($WeekDay / 7) * 7));
-
- :return (($DailyPskSecrets->0->($Day - 1)) . \
- ($DailyPskSecrets->1->($Month - 1)) . \
- ($DailyPskSecrets->2->$WeekDay));
-}
-
-:local Seen [ :toarray "" ];
-:local Date [ / system clock get date ];
-:local NewPsk [ $GeneratePSK $Date ];
-
-:foreach AccList in=[ / caps-man access-list find where comment~$DailyPskMatchComment ] do={
- :local Ssid [ / caps-man access-list get $AccList ssid-regexp ];
- :local Configuration [ / caps-man configuration get ([ find where ssid=$Ssid ]->0) name ];
- :local OldPsk [ / caps-man access-list get $AccList private-passphrase ];
- :local Skip 0;
-
- :if ($NewPsk != $OldPsk) do={
- $LogPrintExit2 info $0 ("Updating daily PSK for " . $Ssid . " to " . $NewPsk . " (was " . $OldPsk . ")") false;
- / caps-man access-list set $AccList private-passphrase=$NewPsk;
-
- :if ([ :len [ / caps-man interface find where configuration=$Configuration ] ] > 0) do={
- :foreach SeenSsid in=$Seen do={
- :if ($SeenSsid = $Ssid) do={
- $LogPrintExit2 debug $0 ("Already sent a mail for SSID " . $Ssid . ", skipping.") false;
- :set Skip 1;
- }
- }
-
- :if ($Skip = 0) do={
- :set Seen ($Seen, $Ssid);
- :local Link ("https://www.eworm.de/cgi-bin/cqrlogo-wifi.cgi" . \
- "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]);
- $SendNotification ([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid) \
- ("This is the daily PSK on " . $Identity . ":\n\n" . \
- "SSID: " . $Ssid . "\n" . \
- "PSK: " . $NewPsk . "\n" . \
- "Date: " . $Date . "\n\n" . \
- "A client device specific rule must not exist!") $Link;
- }
- }
- }
-}
diff --git a/daily-psk.capsman.rsc b/daily-psk.capsman.rsc
new file mode 100644
index 0000000..43651d0
--- /dev/null
+++ b/daily-psk.capsman.rsc
@@ -0,0 +1,92 @@
+#!rsc by RouterOS
+# RouterOS script: daily-psk.capsman
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# Michael Gisbers <michael@gisbers.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+: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={
+ :error false;
+ }
+ $WaitFullyConnected;
+
+ # return pseudo-random string for PSK
+ :local GeneratePSK do={
+ :local Date [ :tostr $1 ];
+
+ :global DailyPskSecrets;
+
+ :global ParseDate;
+
+ :set Date [ $ParseDate $Date ];
+
+ :local A ((14 - ($Date->"month")) / 12);
+ :local B (($Date->"year") - $A);
+ :local C (($Date->"month") + 12 * $A - 2);
+ :local WeekDay (7000 + ($Date->"day") + $B + ($B / 4) - ($B / 100) + ($B / 400) + ((31 * $C) / 12));
+ :set WeekDay ($WeekDay - (($WeekDay / 7) * 7));
+
+ :return (($DailyPskSecrets->0->(($Date->"day") - 1)) . \
+ ($DailyPskSecrets->1->(($Date->"month") - 1)) . \
+ ($DailyPskSecrets->2->$WeekDay));
+ }
+
+ :local Seen ({});
+ :local Date [ /system/clock/get date ];
+ :local NewPsk [ $GeneratePSK $Date ];
+
+ :foreach AccList in=[ /caps-man/access-list/find where comment~$DailyPskMatchComment ] do={
+ :local SsidRegExp [ /caps-man/access-list/get $AccList ssid-regexp ];
+ :local Configuration ([ /caps-man/configuration/find where ssid~$SsidRegExp ]->0);
+ :local Ssid [ /caps-man/configuration/get $Configuration ssid ];
+ :local OldPsk [ /caps-man/access-list/get $AccList private-passphrase ];
+ :local Skip 0;
+
+ :if ($NewPsk != $OldPsk) do={
+ $LogPrint info $ScriptName ("Updating daily PSK for " . $Ssid . " to " . $NewPsk . " (was " . $OldPsk . ")");
+ /caps-man/access-list/set $AccList private-passphrase=$NewPsk;
+
+ :if ([ :len [ /caps-man/actual-interface-configuration/find where configuration.ssid=$Ssid !disabled ] ] > 0) do={
+ :if ($Seen->$Ssid = 1) do={
+ $LogPrint debug $ScriptName ("Already sent a mail for SSID " . $Ssid . ", skipping.");
+ } else={
+ :local Link ($DailyPskQrCodeUrl . \
+ "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]);
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid); \
+ message=("This is the daily PSK on " . $Identity . ":\n\n" . \
+ [ $FormatLine "SSID" $Ssid ] . "\n" . \
+ [ $FormatLine "PSK" $NewPsk ] . "\n" . \
+ [ $FormatLine "Date" $Date ] . "\n\n" . \
+ "A client device specific rule must not exist!"); link=$Link });
+ :set ($Seen->$Ssid) 1;
+ }
+ }
+ }
+ }
+} on-error={ }
diff --git a/daily-psk.local b/daily-psk.local
deleted file mode 100644
index 6186ddc..0000000
--- a/daily-psk.local
+++ /dev/null
@@ -1,93 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: daily-psk.local
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# Michael Gisbers <michael@gisbers.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# update daily PSK (pre shared key)
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/daily-psk.md
-#
-# !! Do not edit this file, it is generated from template!
-
-:local 0 "daily-psk.local";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global DailyPskMatchComment;
-:global Identity;
-
-:global LogPrintExit2;
-:global SendNotification;
-:global SymbolForNotification;
-:global UrlEncode;
-:global WaitForFile;
-:global WaitFullyConnected;
-
-$WaitFullyConnected;
-
-# return pseudo-random string for PSK
-:local GeneratePSK do={
- :local Date [ :tostr $1 ];
-
- :global DailyPskSecrets;
-
- :local Months { "jan"; "feb"; "mar"; "apr"; "may"; "jun";
- "jul"; "aug"; "sep"; "oct"; "nov"; "dec" };
-
- :local Month [ :pick $Date 0 3 ];
- :local Day [ :tonum [ :pick $Date 4 6 ] ];
- :local Year [ :pick $Date 7 11 ];
-
- :for MIndex from=0 to=[ :len $Months ] do={
- :if ($Months->$MIndex = $Month) do={
- :set Month ($MIndex + 1);
- }
- }
-
- :local A ((14 - $Month) / 12);
- :local B ($Year - $A);
- :local C ($Month + 12 * $A - 2);
- :local WeekDay (7000 + $Day + $B + ($B / 4) - ($B / 100) + ($B / 400) + ((31 * $C) / 12));
- :set WeekDay ($WeekDay - (($WeekDay / 7) * 7));
-
- :return (($DailyPskSecrets->0->($Day - 1)) . \
- ($DailyPskSecrets->1->($Month - 1)) . \
- ($DailyPskSecrets->2->$WeekDay));
-}
-
-:local Seen [ :toarray "" ];
-:local Date [ / system clock get date ];
-:local NewPsk [ $GeneratePSK $Date ];
-
-:foreach AccList in=[ / interface wireless access-list find where comment~$DailyPskMatchComment ] do={
- :local IntName [ / interface wireless access-list get $AccList interface ];
- :local Ssid [ / interface wireless get $IntName ssid ];
- :local OldPsk [ / interface wireless access-list get $AccList private-pre-shared-key ];
- :local Skip 0;
-
- :if ($NewPsk != $OldPsk) do={
- $LogPrintExit2 info $0 ("Updating daily PSK for " . $Ssid . " to " . $NewPsk . " (was " . $OldPsk . ")") false;
- / interface wireless access-list set $AccList private-pre-shared-key=$NewPsk;
-
- :if ([ :len [ / interface wireless find where name=$IntName disabled=no ] ] = 1) do={
- :foreach SeenSsid in=$Seen do={
- :if ($SeenSsid = $Ssid) do={
- $LogPrintExit2 debug $0 ("Already sent a mail for SSID " . $Ssid . ", skipping.") false;
- :set Skip 1;
- }
- }
-
- :if ($Skip = 0) do={
- :set Seen ($Seen, $Ssid);
- :local Link ("https://www.eworm.de/cgi-bin/cqrlogo-wifi.cgi" . \
- "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]);
- $SendNotification ([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid) \
- ("This is the daily PSK on " . $Identity . ":\n\n" . \
- "SSID: " . $Ssid . "\n" . \
- "PSK: " . $NewPsk . "\n" . \
- "Date: " . $Date . "\n\n" . \
- "A client device specific rule must not exist!") $Link;
- }
- }
- }
-}
diff --git a/daily-psk.local.rsc b/daily-psk.local.rsc
new file mode 100644
index 0000000..2dbc61b
--- /dev/null
+++ b/daily-psk.local.rsc
@@ -0,0 +1,91 @@
+#!rsc by RouterOS
+# RouterOS script: daily-psk.local
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# Michael Gisbers <michael@gisbers.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+: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={
+ :error false;
+ }
+ $WaitFullyConnected;
+
+ # return pseudo-random string for PSK
+ :local GeneratePSK do={
+ :local Date [ :tostr $1 ];
+
+ :global DailyPskSecrets;
+
+ :global ParseDate;
+
+ :set Date [ $ParseDate $Date ];
+
+ :local A ((14 - ($Date->"month")) / 12);
+ :local B (($Date->"year") - $A);
+ :local C (($Date->"month") + 12 * $A - 2);
+ :local WeekDay (7000 + ($Date->"day") + $B + ($B / 4) - ($B / 100) + ($B / 400) + ((31 * $C) / 12));
+ :set WeekDay ($WeekDay - (($WeekDay / 7) * 7));
+
+ :return (($DailyPskSecrets->0->(($Date->"day") - 1)) . \
+ ($DailyPskSecrets->1->(($Date->"month") - 1)) . \
+ ($DailyPskSecrets->2->$WeekDay));
+ }
+
+ :local Seen ({});
+ :local Date [ /system/clock/get date ];
+ :local NewPsk [ $GeneratePSK $Date ];
+
+ :foreach AccList in=[ /interface/wireless/access-list/find where comment~$DailyPskMatchComment ] do={
+ :local IntName [ /interface/wireless/access-list/get $AccList interface ];
+ :local Ssid [ /interface/wireless/get $IntName ssid ];
+ :local OldPsk [ /interface/wireless/access-list/get $AccList private-pre-shared-key ];
+ :local Skip 0;
+
+ :if ($NewPsk != $OldPsk) do={
+ $LogPrint info $ScriptName ("Updating daily PSK for " . $Ssid . " to " . $NewPsk . " (was " . $OldPsk . ")");
+ /interface/wireless/access-list/set $AccList private-pre-shared-key=$NewPsk;
+
+ :if ([ :len [ /interface/wireless/find where name=$IntName !disabled ] ] = 1) do={
+ :if ($Seen->$Ssid = 1) do={
+ $LogPrint debug $ScriptName ("Already sent a mail for SSID " . $Ssid . ", skipping.");
+ } else={
+ :local Link ($DailyPskQrCodeUrl . \
+ "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]);
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid); \
+ message=("This is the daily PSK on " . $Identity . ":\n\n" . \
+ [ $FormatLine "SSID" $Ssid ] . "\n" . \
+ [ $FormatLine "PSK" $NewPsk ] . "\n" . \
+ [ $FormatLine "Date" $Date ] . "\n\n" . \
+ "A client device specific rule must not exist!"); link=$Link });
+ :set ($Seen->$Ssid) 1;
+ }
+ }
+ }
+ }
+} on-error={ }
diff --git a/daily-psk.template b/daily-psk.template
deleted file mode 100644
index 42abba5..0000000
--- a/daily-psk.template
+++ /dev/null
@@ -1,99 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: daily-psk%TEMPL%
-# Copyright (c) 2013-2021 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
-#
-# !! This is just a template! Replace '%PATH%' with 'caps-man'
-# !! or 'interface wireless'!
-
-:local 0 "daily-psk%TEMPL%";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global DailyPskMatchComment;
-:global Identity;
-
-:global LogPrintExit2;
-:global SendNotification;
-:global SymbolForNotification;
-:global UrlEncode;
-:global WaitForFile;
-:global WaitFullyConnected;
-
-$WaitFullyConnected;
-
-# return pseudo-random string for PSK
-:local GeneratePSK do={
- :local Date [ :tostr $1 ];
-
- :global DailyPskSecrets;
-
- :local Months { "jan"; "feb"; "mar"; "apr"; "may"; "jun";
- "jul"; "aug"; "sep"; "oct"; "nov"; "dec" };
-
- :local Month [ :pick $Date 0 3 ];
- :local Day [ :tonum [ :pick $Date 4 6 ] ];
- :local Year [ :pick $Date 7 11 ];
-
- :for MIndex from=0 to=[ :len $Months ] do={
- :if ($Months->$MIndex = $Month) do={
- :set Month ($MIndex + 1);
- }
- }
-
- :local A ((14 - $Month) / 12);
- :local B ($Year - $A);
- :local C ($Month + 12 * $A - 2);
- :local WeekDay (7000 + $Day + $B + ($B / 4) - ($B / 100) + ($B / 400) + ((31 * $C) / 12));
- :set WeekDay ($WeekDay - (($WeekDay / 7) * 7));
-
- :return (($DailyPskSecrets->0->($Day - 1)) . \
- ($DailyPskSecrets->1->($Month - 1)) . \
- ($DailyPskSecrets->2->$WeekDay));
-}
-
-:local Seen [ :toarray "" ];
-:local Date [ / system clock get date ];
-:local NewPsk [ $GeneratePSK $Date ];
-
-:foreach AccList in=[ / %PATH% access-list find where comment~$DailyPskMatchComment ] do={
- :local IntName [ / interface wireless access-list get $AccList interface ];
- :local Ssid [ / interface wireless get $IntName ssid ];
- :local Ssid [ / caps-man access-list get $AccList ssid-regexp ];
- :local Configuration [ / caps-man configuration get ([ find where ssid=$Ssid ]->0) name ];
- :local OldPsk [ / interface wireless access-list get $AccList private-pre-shared-key ];
- :local OldPsk [ / caps-man access-list get $AccList private-passphrase ];
- :local Skip 0;
-
- :if ($NewPsk != $OldPsk) do={
- $LogPrintExit2 info $0 ("Updating daily PSK for " . $Ssid . " to " . $NewPsk . " (was " . $OldPsk . ")") false;
- / interface wireless access-list set $AccList private-pre-shared-key=$NewPsk;
- / caps-man access-list set $AccList private-passphrase=$NewPsk;
-
- :if ([ :len [ / interface wireless find where name=$IntName disabled=no ] ] = 1) do={
- :if ([ :len [ / caps-man interface find where configuration=$Configuration ] ] > 0) do={
- :foreach SeenSsid in=$Seen do={
- :if ($SeenSsid = $Ssid) do={
- $LogPrintExit2 debug $0 ("Already sent a mail for SSID " . $Ssid . ", skipping.") false;
- :set Skip 1;
- }
- }
-
- :if ($Skip = 0) do={
- :set Seen ($Seen, $Ssid);
- :local Link ("https://www.eworm.de/cgi-bin/cqrlogo-wifi.cgi" . \
- "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]);
- $SendNotification ([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid) \
- ("This is the daily PSK on " . $Identity . ":\n\n" . \
- "SSID: " . $Ssid . "\n" . \
- "PSK: " . $NewPsk . "\n" . \
- "Date: " . $Date . "\n\n" . \
- "A client device specific rule must not exist!") $Link;
- }
- }
- }
-}
diff --git a/daily-psk.template.rsc b/daily-psk.template.rsc
new file mode 100644
index 0000000..e190ffb
--- /dev/null
+++ b/daily-psk.template.rsc
@@ -0,0 +1,107 @@
+#!rsc by RouterOS
+# RouterOS script: daily-psk%TEMPL%
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# Michael Gisbers <michael@gisbers.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# update daily PSK (pre shared key)
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/daily-psk.md
+#
+# !! This is just a template to generate the real script!
+# !! Pattern '%TEMPL%' is replaced, paths are filtered.
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+: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={
+ :error false;
+ }
+ $WaitFullyConnected;
+
+ # return pseudo-random string for PSK
+ :local GeneratePSK do={
+ :local Date [ :tostr $1 ];
+
+ :global DailyPskSecrets;
+
+ :global ParseDate;
+
+ :set Date [ $ParseDate $Date ];
+
+ :local A ((14 - ($Date->"month")) / 12);
+ :local B (($Date->"year") - $A);
+ :local C (($Date->"month") + 12 * $A - 2);
+ :local WeekDay (7000 + ($Date->"day") + $B + ($B / 4) - ($B / 100) + ($B / 400) + ((31 * $C) / 12));
+ :set WeekDay ($WeekDay - (($WeekDay / 7) * 7));
+
+ :return (($DailyPskSecrets->0->(($Date->"day") - 1)) . \
+ ($DailyPskSecrets->1->(($Date->"month") - 1)) . \
+ ($DailyPskSecrets->2->$WeekDay));
+ }
+
+ :local Seen ({});
+ :local Date [ /system/clock/get date ];
+ :local NewPsk [ $GeneratePSK $Date ];
+
+ :foreach AccList in=[ /caps-man/access-list/find where comment~$DailyPskMatchComment ] do={
+ :foreach AccList in=[ /interface/wifi/access-list/find where comment~$DailyPskMatchComment ] do={
+ :foreach AccList in=[ /interface/wireless/access-list/find where comment~$DailyPskMatchComment ] do={
+ :local SsidRegExp [ /caps-man/access-list/get $AccList ssid-regexp ];
+ :local SsidRegExp [ /interface/wifi/access-list/get $AccList ssid-regexp ];
+ :local Configuration ([ /caps-man/configuration/find where ssid~$SsidRegExp ]->0);
+ :local Configuration ([ /interface/wifi/configuration/find where ssid~$SsidRegExp ]->0);
+ :local Ssid [ /caps-man/configuration/get $Configuration ssid ];
+ :local Ssid [ /interface/wifi/configuration/get $Configuration ssid ];
+ :local OldPsk [ /caps-man/access-list/get $AccList private-passphrase ];
+ :local OldPsk [ /interface/wifi/access-list/get $AccList passphrase ];
+ # /caps-man/ /interface/wifi/ above - /interface/wireless/ below
+ :local IntName [ /interface/wireless/access-list/get $AccList interface ];
+ :local Ssid [ /interface/wireless/get $IntName ssid ];
+ :local OldPsk [ /interface/wireless/access-list/get $AccList private-pre-shared-key ];
+ :local Skip 0;
+
+ :if ($NewPsk != $OldPsk) do={
+ $LogPrint info $ScriptName ("Updating daily PSK for " . $Ssid . " to " . $NewPsk . " (was " . $OldPsk . ")");
+ /caps-man/access-list/set $AccList private-passphrase=$NewPsk;
+ /interface/wifi/access-list/set $AccList passphrase=$NewPsk;
+ /interface/wireless/access-list/set $AccList private-pre-shared-key=$NewPsk;
+
+ :if ([ :len [ /caps-man/actual-interface-configuration/find where configuration.ssid=$Ssid !disabled ] ] > 0) do={
+ :if ([ :len [ /interface/wifi/actual-configuration/find where configuration.ssid=$Ssid ] ] > 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 ] . "\n" . \
+ [ $FormatLine "PSK" $NewPsk ] . "\n" . \
+ [ $FormatLine "Date" $Date ] . "\n\n" . \
+ "A client device specific rule must not exist!"); link=$Link });
+ :set ($Seen->$Ssid) 1;
+ }
+ }
+ }
+ }
+} on-error={ }
diff --git a/daily-psk.wifi.rsc b/daily-psk.wifi.rsc
new file mode 100644
index 0000000..ee3e1b0
--- /dev/null
+++ b/daily-psk.wifi.rsc
@@ -0,0 +1,92 @@
+#!rsc by RouterOS
+# RouterOS script: daily-psk.wifi
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# Michael Gisbers <michael@gisbers.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+: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={
+ :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/actual-configuration/find where configuration.ssid=$Ssid ] ] > 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 ] . "\n" . \
+ [ $FormatLine "PSK" $NewPsk ] . "\n" . \
+ [ $FormatLine "Date" $Date ] . "\n\n" . \
+ "A client device specific rule must not exist!"); link=$Link });
+ :set ($Seen->$Ssid) 1;
+ }
+ }
+ }
+ }
+} on-error={ }
diff --git a/dhcp-lease-comment.capsman b/dhcp-lease-comment.capsman
deleted file mode 100644
index 96b1741..0000000
--- a/dhcp-lease-comment.capsman
+++ /dev/null
@@ -1,28 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: dhcp-lease-comment.capsman
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# update dhcp-server lease comment with infos from access-list
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-lease-comment.md
-#
-# !! Do not edit this file, it is generated from template!
-
-:local 0 "dhcp-lease-comment.capsman";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global LogPrintExit2;
-
-:foreach Lease in=[ / ip dhcp-server lease find where dynamic=yes status=bound ] do={
- :local LeaseVal [ / ip dhcp-server lease get $Lease ];
- :local NewComment;
- :local AccessList ([ / caps-man access-list find where mac-address=($LeaseVal->"mac-address") ]->0);
- :if ([ :len $AccessList ] > 0) do={
- :set NewComment [ / caps-man access-list get $AccessList comment ];
- }
- :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={
- $LogPrintExit2 info $0 ("Updating comment for DHCP lease " . $LeaseVal->"mac-address" . ": " . $NewComment) false;
- / ip dhcp-server lease set comment=$NewComment $Lease;
- }
-}
diff --git a/dhcp-lease-comment.capsman.rsc b/dhcp-lease-comment.capsman.rsc
new file mode 100644
index 0000000..4ac228b
--- /dev/null
+++ b/dhcp-lease-comment.capsman.rsc
@@ -0,0 +1,39 @@
+#!rsc by RouterOS
+# RouterOS script: dhcp-lease-comment.capsman
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: lease-script, order=60
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global LogPrint;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :foreach Lease in=[ /ip/dhcp-server/lease/find where dynamic=yes status=bound ] do={
+ :local LeaseVal [ /ip/dhcp-server/lease/get $Lease ];
+ :local NewComment;
+ :local AccessList ([ /caps-man/access-list/find where mac-address=($LeaseVal->"active-mac-address") ]->0);
+ :if ([ :len $AccessList ] > 0) do={
+ :set NewComment [ /caps-man/access-list/get $AccessList comment ];
+ }
+ :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={
+ $LogPrint info $ScriptName ("Updating comment for DHCP lease " . $LeaseVal->"active-mac-address" . ": " . $NewComment);
+ /ip/dhcp-server/lease/set comment=$NewComment $Lease;
+ }
+ }
+} on-error={ }
diff --git a/dhcp-lease-comment.local b/dhcp-lease-comment.local
deleted file mode 100644
index 74de2c8..0000000
--- a/dhcp-lease-comment.local
+++ /dev/null
@@ -1,28 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: dhcp-lease-comment.local
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# update dhcp-server lease comment with infos from access-list
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-lease-comment.md
-#
-# !! Do not edit this file, it is generated from template!
-
-:local 0 "dhcp-lease-comment.local";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global LogPrintExit2;
-
-:foreach Lease in=[ / ip dhcp-server lease find where dynamic=yes status=bound ] do={
- :local LeaseVal [ / ip dhcp-server lease get $Lease ];
- :local NewComment;
- :local AccessList ([ / interface wireless access-list find where mac-address=($LeaseVal->"mac-address") ]->0);
- :if ([ :len $AccessList ] > 0) do={
- :set NewComment [ / interface wireless access-list get $AccessList comment ];
- }
- :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={
- $LogPrintExit2 info $0 ("Updating comment for DHCP lease " . $LeaseVal->"mac-address" . ": " . $NewComment) false;
- / ip dhcp-server lease set comment=$NewComment $Lease;
- }
-}
diff --git a/dhcp-lease-comment.local.rsc b/dhcp-lease-comment.local.rsc
new file mode 100644
index 0000000..a49f74f
--- /dev/null
+++ b/dhcp-lease-comment.local.rsc
@@ -0,0 +1,39 @@
+#!rsc by RouterOS
+# RouterOS script: dhcp-lease-comment.local
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: lease-script, order=60
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global LogPrint;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :foreach Lease in=[ /ip/dhcp-server/lease/find where dynamic=yes status=bound ] do={
+ :local LeaseVal [ /ip/dhcp-server/lease/get $Lease ];
+ :local NewComment;
+ :local AccessList ([ /interface/wireless/access-list/find where mac-address=($LeaseVal->"active-mac-address") ]->0);
+ :if ([ :len $AccessList ] > 0) do={
+ :set NewComment [ /interface/wireless/access-list/get $AccessList comment ];
+ }
+ :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={
+ $LogPrint info $ScriptName ("Updating comment for DHCP lease " . $LeaseVal->"active-mac-address" . ": " . $NewComment);
+ /ip/dhcp-server/lease/set comment=$NewComment $Lease;
+ }
+ }
+} on-error={ }
diff --git a/dhcp-lease-comment.template b/dhcp-lease-comment.template
deleted file mode 100644
index 0e31007..0000000
--- a/dhcp-lease-comment.template
+++ /dev/null
@@ -1,29 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: dhcp-lease-comment%TEMPL%
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# update dhcp-server lease comment with infos from access-list
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-lease-comment.md
-#
-# !! This is just a template! Replace '%PATH%' with 'caps-man'
-# !! or 'interface wireless'!
-
-:local 0 "dhcp-lease-comment%TEMPL%";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global LogPrintExit2;
-
-:foreach Lease in=[ / ip dhcp-server lease find where dynamic=yes status=bound ] do={
- :local LeaseVal [ / ip dhcp-server lease get $Lease ];
- :local NewComment;
- :local AccessList ([ / %PATH% access-list find where mac-address=($LeaseVal->"mac-address") ]->0);
- :if ([ :len $AccessList ] > 0) do={
- :set NewComment [ / %PATH% access-list get $AccessList comment ];
- }
- :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={
- $LogPrintExit2 info $0 ("Updating comment for DHCP lease " . $LeaseVal->"mac-address" . ": " . $NewComment) false;
- / ip dhcp-server lease set comment=$NewComment $Lease;
- }
-}
diff --git a/dhcp-lease-comment.template.rsc b/dhcp-lease-comment.template.rsc
new file mode 100644
index 0000000..0f0975b
--- /dev/null
+++ b/dhcp-lease-comment.template.rsc
@@ -0,0 +1,44 @@
+#!rsc by RouterOS
+# RouterOS script: dhcp-lease-comment%TEMPL%
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: lease-script, order=60
+# requires RouterOS, version=7.12
+#
+# update dhcp-server lease comment with infos from access-list
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-lease-comment.md
+#
+# !! This is just a template to generate the real script!
+# !! Pattern '%TEMPL%' is replaced, paths are filtered.
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global LogPrint;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :foreach Lease in=[ /ip/dhcp-server/lease/find where dynamic=yes status=bound ] do={
+ :local LeaseVal [ /ip/dhcp-server/lease/get $Lease ];
+ :local NewComment;
+ :local AccessList ([ /caps-man/access-list/find where mac-address=($LeaseVal->"active-mac-address") ]->0);
+ :local AccessList ([ /interface/wifi/access-list/find where mac-address=($LeaseVal->"active-mac-address") ]->0);
+ :local AccessList ([ /interface/wireless/access-list/find where mac-address=($LeaseVal->"active-mac-address") ]->0);
+ :if ([ :len $AccessList ] > 0) do={
+ :set NewComment [ /caps-man/access-list/get $AccessList comment ];
+ :set NewComment [ /interface/wifi/access-list/get $AccessList comment ];
+ :set NewComment [ /interface/wireless/access-list/get $AccessList comment ];
+ }
+ :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={
+ $LogPrint info $ScriptName ("Updating comment for DHCP lease " . $LeaseVal->"active-mac-address" . ": " . $NewComment);
+ /ip/dhcp-server/lease/set comment=$NewComment $Lease;
+ }
+ }
+} on-error={ }
diff --git a/dhcp-lease-comment.wifi.rsc b/dhcp-lease-comment.wifi.rsc
new file mode 100644
index 0000000..c9c091b
--- /dev/null
+++ b/dhcp-lease-comment.wifi.rsc
@@ -0,0 +1,39 @@
+#!rsc by RouterOS
+# RouterOS script: dhcp-lease-comment.wifi
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: lease-script, order=60
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global LogPrint;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :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={ }
diff --git a/dhcp-to-dns b/dhcp-to-dns
deleted file mode 100644
index dfb438f..0000000
--- a/dhcp-to-dns
+++ /dev/null
@@ -1,78 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: dhcp-to-dns
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# check DHCP leases and add/remove/update DNS entries
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-to-dns.md
-
-:local 0 "dhcp-to-dns";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global Domain;
-:global HostNameInZone;
-:global Identity;
-:global PrefixInZone;
-:global ServerNameInZone;
-
-:global CharacterReplace;
-:global IfThenElse;
-:global LogPrintExit2;
-
-:local Zone \
- ([ $IfThenElse ($PrefixInZone = true) "dhcp." ] . \
- [ $IfThenElse ($HostNameInZone = true) ($Identity . ".") ] . $Domain);
-:local Ttl 5m;
-:local CommentPrefix "managed by dhcp-to-dns for ";
-
-:if ([ :len [ / ip dns static find where comment="--- dhcp-to-dns above ---" name=- type=NXDOMAIN disabled ] ] = 0) do={
- / ip dns static add comment="--- dhcp-to-dns above ---" name=- type=NXDOMAIN disabled=yes;
- $LogPrintExit2 warning $0 ("Added disabled static dns record with comment '--- dhcp-to-dns above ---'.") false;
-}
-:local PlaceBefore ([ / ip dns static find where comment="--- dhcp-to-dns above ---" name=- type=NXDOMAIN disabled ]->0);
-
-:foreach DnsRecord in=[ / ip dns static find where comment ~ $CommentPrefix ] do={
- :local DnsRecordVal [ / ip dns static get $DnsRecord ];
- :local MacAddress [ $CharacterReplace ($DnsRecordVal->"comment") $CommentPrefix "" ];
- :if ([ :len [ / ip dhcp-server lease find where mac-address=$MacAddress address=($DnsRecordVal->"address") status=bound ] ] > 0) do={
- $LogPrintExit2 debug $0 ("Lease for " . $MacAddress . " (" . $DnsRecordVal->"name" . ") still exists. Not deleting DNS entry.") false;
- } else={
- :local Found false;
- $LogPrintExit2 info $0 ("Lease expired for " . $MacAddress . " (" . $DnsRecordVal->"name" . "), deleting DNS entry.") false;
- / ip dns static remove $DnsRecord;
- }
-}
-
-:foreach Lease in=[ / ip dhcp-server lease find where status=bound ] do={
- :local LeaseVal [ / ip dhcp-server lease get $Lease ];
- :local Comment ($CommentPrefix . $LeaseVal->"mac-address");
- :local HostName [ $IfThenElse ([ :len ($LeaseVal->"host-name") ] = 0) \
- [ $CharacterReplace ($LeaseVal->"mac-address") ":" "-" ] \
- [ $CharacterReplace ($LeaseVal->"host-name") " " "" ] ];
-
- :local Fqdn ($HostName . "." . [ $IfThenElse ($ServerNameInZone = true) ($LeaseVal->"server" . ".") ] . $Zone);
- :local DnsRecord [ / ip dns static find where name=$Fqdn ];
- :if ([ :len $DnsRecord ] > 0) do={
- :local DnsIp [ / ip dns static get $DnsRecord address ];
-
- :local DupMacLeases [ / ip dhcp-server lease find where mac-address=($LeaseVal->"mac-address") status=bound ];
- :if ([ :len $DupMacLeases ] > 1) do={
- :set ($LeaseVal->"address") [ / ip dhcp-server lease get ($DupMacLeases->([ :len $DupMacLeases ] - 1)) address ];
- }
-
- :if ([ :len ($LeaseVal->"host-name") ] > 0) do={
- :set ($LeaseVal->"address") [ / ip dhcp-server lease get ([ find where host-name=($LeaseVal->"host-name") status=bound ]->0) address ];
- }
-
- :if ($DnsIp = $LeaseVal->"address") do={
- $LogPrintExit2 debug $0 ("DNS entry for " . $Fqdn . " does not need updating.") false;
- } else={
- $LogPrintExit2 info $0 ("Replacing DNS entry for " . $Fqdn . ", new address is " . $LeaseVal->"address" . ".") false;
- / ip dns static set name=$Fqdn address=($LeaseVal->"address") ttl=$Ttl comment=$Comment $DnsRecord;
- }
- } else={
- $LogPrintExit2 info $0 ("Adding new DNS entry for " . $Fqdn . ", address is " . $LeaseVal->"address" . ".") false;
- / ip dns static add name=$Fqdn address=($LeaseVal->"address") ttl=$Ttl comment=$Comment place-before=$PlaceBefore;
- }
-}
diff --git a/dhcp-to-dns.rsc b/dhcp-to-dns.rsc
new file mode 100644
index 0000000..5b6e64a
--- /dev/null
+++ b/dhcp-to-dns.rsc
@@ -0,0 +1,126 @@
+#!rsc by RouterOS
+# RouterOS script: dhcp-to-dns
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: lease-script, order=20
+# requires RouterOS, version=7.12
+#
+# check DHCP leases and add/remove/update DNS entries
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-to-dns.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global Domain;
+ :global Identity;
+
+ :global CleanName;
+ :global EitherOr;
+ :global IfThenElse;
+ :global LogPrint;
+ :global LogPrintOnce;
+ :global ParseKeyValueStore;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName 10 ] = false) do={
+ :error false;
+ }
+
+ :local Ttl 5m;
+ :local CommentPrefix ("managed by " . $ScriptName);
+ :local CommentString ("--- " . $ScriptName . " above ---");
+
+ :if ([ :len [ /ip/dns/static/find where (name=$CommentString or (comment=$CommentString and name=-)) type=NXDOMAIN disabled ] ] = 0) do={
+ /ip/dns/static/add name=$CommentString type=NXDOMAIN disabled=yes;
+ $LogPrint warning $ScriptName ("Added disabled static dns record with name '" . $CommentString . "'.");
+ }
+ :local PlaceBefore ([ /ip/dns/static/find where (name=$CommentString or (comment=$CommentString and name=-)) type=NXDOMAIN disabled ]->0);
+
+ :foreach DnsRecord in=[ /ip/dns/static/find where comment~("^" . $CommentPrefix . "\\b") (!type or type=A) ] do={
+ :local DnsRecordVal [ /ip/dns/static/get $DnsRecord ];
+ :local DnsRecordInfo [ $ParseKeyValueStore ($DnsRecordVal->"comment") ];
+ :local MacInServer ($DnsRecordInfo->"macaddress" . " in " . $DnsRecordInfo->"server");
+
+ :if ([ :len [ /ip/dhcp-server/lease/find where active-mac-address=($DnsRecordInfo->"macaddress") \
+ active-address=($DnsRecordVal->"address") server=($DnsRecordInfo->"server") status=bound ] ] > 0) do={
+ $LogPrint debug $ScriptName ("Lease for " . $MacInServer . " (" . $DnsRecordVal->"name" . ") still exists. Not deleting record.");
+ } else={
+ :local Found false;
+ $LogPrint info $ScriptName ("Lease expired for " . $MacInServer . ", deleting record (" . $DnsRecordVal->"name" . ").");
+ /ip/dns/static/remove $DnsRecord;
+ /ip/dns/static/remove [ find where type=CNAME comment=($DnsRecordVal->"comment") ];
+ }
+ }
+
+ :foreach Lease in=[ /ip/dhcp-server/lease/find where status=bound ] do={
+ :local LeaseVal;
+ :do {
+ :set LeaseVal [ /ip/dhcp-server/lease/get $Lease ];
+ :if ([ :len [ /ip/dhcp-server/lease/find where active-mac-address=($LeaseVal->"active-mac-address") status=bound ] ] > 1) do={
+ $LogPrintOnce info $ScriptName ("Multiple bound leases found for mac-address " . ($LeaseVal->"active-mac-address") . "!");
+ }
+ } on-error={
+ $LogPrint debug $ScriptName ("A lease just vanished, ignoring.");
+ }
+
+ :if ([ :len ($LeaseVal->"active-address") ] > 0) do={
+ :local Comment ($CommentPrefix . ", macaddress=" . $LeaseVal->"active-mac-address" . ", server=" . $LeaseVal->"server");
+ :local MacDash [ $CleanName ($LeaseVal->"active-mac-address") ];
+ :local HostName [ $CleanName [ $EitherOr ([ $ParseKeyValueStore ($LeaseVal->"comment") ]->"hostname") ($LeaseVal->"host-name") ] ];
+ :local Network [ /ip/dhcp-server/network/find where ($LeaseVal->"active-address") in address ];
+ :local NetworkVal;
+ :if ([ :len $Network ] > 0) do={
+ :set NetworkVal [ /ip/dhcp-server/network/get ($Network->0) ];
+ }
+ :local NetworkInfo [ $ParseKeyValueStore ($NetworkVal->"comment") ];
+ :local NetDomain ([ $IfThenElse ([ :len ($NetworkInfo->"name-extra") ] > 0) ($NetworkInfo->"name-extra" . ".") ] . \
+ [ $EitherOr [ $EitherOr ($NetworkInfo->"domain") ($NetworkVal->"domain") ] $Domain ]);
+ :local FullA ($MacDash . "." . $NetDomain);
+ :local FullCN ($HostName . "." . $NetDomain);
+ :local MacInServer ($LeaseVal->"active-mac-address" . " in " . $LeaseVal->"server");
+
+ :local DnsRecord [ /ip/dns/static/find where comment=$Comment (!type or type=A) ];
+ :if ([ :len $DnsRecord ] > 0) do={
+ :local DnsRecordVal [ /ip/dns/static/get $DnsRecord ];
+
+ :if ($DnsRecordVal->"address" = $LeaseVal->"active-address" && $DnsRecordVal->"name" = $FullA) do={
+ $LogPrint debug $ScriptName ("The A record for " . $MacInServer . " (" . $FullA . ") does not need updating.");
+ } else={
+ $LogPrint info $ScriptName ("Updating A record for " . $MacInServer . " (" . $FullA . " -> " . $LeaseVal->"active-address" . ").");
+ /ip/dns/static/set address=($LeaseVal->"active-address") name=$FullA $DnsRecord;
+ }
+
+ :local CName [ /ip/dns/static/find where comment=$Comment type=CNAME ];
+ :if ([ :len $CName ] > 0) do={
+ :local CNameVal [ /ip/dns/static/get $CName ];
+ :if ($CNameVal->"name" != $FullCN || $CNameVal->"cname" != $FullA) do={
+ $LogPrint info $ScriptName ("Deleting CNAME record with wrong data for " . $MacInServer . ".");
+ /ip/dns/static/remove $CName;
+ }
+ }
+ :if ([ :len $HostName ] > 0 && [ :len [ /ip/dns/static/find where name=$FullCN type=CNAME ] ] = 0) do={
+ $LogPrint info $ScriptName ("Adding CNAME record for " . $MacInServer . " (" . $FullCN . " -> " . $FullA . ").");
+ /ip/dns/static/add name=$FullCN type=CNAME cname=$FullA ttl=$Ttl comment=$Comment place-before=$PlaceBefore;
+ }
+
+ } else={
+ $LogPrint info $ScriptName ("Adding A record for " . $MacInServer . " (" . $FullA . " -> " . $LeaseVal->"active-address" . ").");
+ /ip/dns/static/add name=$FullA type=A address=($LeaseVal->"active-address") ttl=$Ttl comment=$Comment place-before=$PlaceBefore;
+ :if ([ :len $HostName ] > 0 && [ :len [ /ip/dns/static/find where name=$FullCN type=CNAME ] ] = 0) do={
+ $LogPrint info $ScriptName ("Adding CNAME record for " . $MacInServer . " (" . $FullCN . " -> " . $FullA . ").");
+ /ip/dns/static/add name=$FullCN type=CNAME cname=$FullA ttl=$Ttl comment=$Comment place-before=$PlaceBefore;
+ }
+ }
+
+ :if ([ :len [ /ip/dns/static/find where name=$FullA (!type or type=A) ] ] > 1) do={
+ $LogPrintOnce warning $ScriptName ("The name '" . $FullA . "' appeared in more than one A record!");
+ }
+ } else={
+ $LogPrint debug $ScriptName ("No address available... Ignoring.");
+ }
+ }
+} on-error={ }
diff --git a/doc/accesslist-duplicates.d/01-example.avif b/doc/accesslist-duplicates.d/01-example.avif
new file mode 100644
index 0000000..11b3fc5
--- /dev/null
+++ b/doc/accesslist-duplicates.d/01-example.avif
Binary files differ
diff --git a/doc/accesslist-duplicates.md b/doc/accesslist-duplicates.md
index 2189322..109bebf 100644
--- a/doc/accesslist-duplicates.md
+++ b/doc/accesslist-duplicates.md
@@ -1,7 +1,17 @@
Find and remove access list duplicates
======================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -12,14 +22,19 @@ entries in wireless access list.
Requirements and installation
-----------------------------
-Depending on whether you use CAPsMAN (`/ caps-man`) or local wireless
-interface (`/ interface wireless`) you need to install a different script.
+Depending on whether you use `wifi` package (`/interface/wifi`), legacy
+wifi with CAPsMAN (`/caps-man`) or local wireless interface
+(`/interface/wireless`) you need to install a different script.
+
+For `wifi`:
-For CAPsMAN:
+ $ScriptInstallUpdate accesslist-duplicates.wifi;
+
+For legacy CAPsMAN:
$ScriptInstallUpdate accesslist-duplicates.capsman;
-For local interface:
+For legacy local interface:
$ScriptInstallUpdate accesslist-duplicates.local;
@@ -28,16 +43,9 @@ Usage and invocation
Run this script from a terminal:
- [admin@kalyke] > / system script run accesslist-duplicates.local
- Flags: X - disabled
- 0 ;;; First entry with identical mac address...
- mac-address=00:11:22:33:44:55 interface=any signal-range=-120..120 allow-signal-out-of-range=10s authentication=yes forwarding=yes ap-tx-limit=0 client-tx-limit=0 private-algo=none private-key="" private-pre-shared-key="" management-protection-key="" vlan-mode=default vlan-id=1
-
- 1 ;;; Second entry with identical mac address...
- mac-address=00:11:22:33:44:55 interface=any signal-range=-120..120 allow-signal-out-of-range=10s authentication=yes forwarding=yes ap-tx-limit=0 client-tx-limit=0 private-algo=none private-key="" private-pre-shared-key="" management-protection-key="" vlan-mode=default vlan-id=1
+ /system/script/run accesslist-duplicates.wifi;
- Numeric id to remove, any key to skip!
- Removing numeric id 1...
+![screenshot: example](accesslist-duplicates.d/01-example.avif)
See also
--------
@@ -45,5 +53,5 @@ See also
* [Collect MAC addresses in wireless access list](collect-wireless-mac.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/backup-cloud.d/notification.avif b/doc/backup-cloud.d/notification.avif
new file mode 100644
index 0000000..e533908
--- /dev/null
+++ b/doc/backup-cloud.d/notification.avif
Binary files differ
diff --git a/doc/backup-cloud.md b/doc/backup-cloud.md
new file mode 100644
index 0000000..03d5953
--- /dev/null
+++ b/doc/backup-cloud.md
@@ -0,0 +1,76 @@
+Upload backup to Mikrotik cloud
+===============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script uploads
+[binary backup to Mikrotik cloud](https://wiki.mikrotik.com/wiki/Manual:IP/Cloud#Backup).
+
+> ⚠️ **Warning**: The used command can hit errors that a script can with
+> workaround only. A notification *should* be sent anyway. But it can result
+> in malfunction of fetch command (where all up- and downloads break) for
+> some time. Failed notifications are queued then.
+
+### Sample notification
+
+![backup-cloud notification](backup-cloud.d/notification.avif)
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+ $ScriptInstallUpdate backup-cloud;
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `BackupPassword`: password to encrypt the backup with
+* `BackupRandomDelay`: delay up to amount of seconds when run from scheduler
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+Usage and invocation
+--------------------
+
+Just run the script:
+
+ /system/script/run backup-cloud;
+
+Creating a scheduler may be an option:
+
+ /system/scheduler/add interval=1w name=backup-cloud on-event="/system/script/run backup-cloud;" start-time=09:20:00;
+
+See also
+--------
+
+* [Send backup via e-mail](backup-email.md)
+* [Save configuration to fallback partition](doc/backup-partition.md)
+* [Upload backup to server](backup-upload.md)
+
+---
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/backup-email.md b/doc/backup-email.md
new file mode 100644
index 0000000..56b0540
--- /dev/null
+++ b/doc/backup-email.md
@@ -0,0 +1,68 @@
+Send backup via e-mail
+======================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script sends binary backup (`/system/backup/save`) and complete
+configuration export (`/export terse show-sensitive`) via e-mail.
+
+Requirements and installation
+-----------------------------
+
+Just install the script and the required module:
+
+ $ScriptInstallUpdate mod/notification-email,backup-email;
+
+Also make sure you configure
+[sending notifications via e-mail](mod/notification-email.md).
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `BackupSendBinary`: whether to send binary backup
+* `BackupSendExport`: whether to send configuration export
+* `BackupSendGlobalConfig`: whether to send `global-config-overlay`
+* `BackupPassword`: password to encrypt the backup with
+* `BackupRandomDelay`: delay up to amount of seconds when run from scheduler
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Usage and invocation
+--------------------
+
+Just run the script:
+
+ /system/script/run backup-email;
+
+Creating a scheduler may be an option:
+
+ /system/scheduler/add interval=1w name=backup-email on-event="/system/script/run backup-email;" start-time=09:15:00;
+
+See also
+--------
+
+* [Upload backup to Mikrotik cloud](backup-cloud.md)
+* [Save configuration to fallback partition](doc/backup-partition.md)
+* [Send notifications via e-mail](mod/notification-email.md)
+* [Upload backup to server](backup-upload.md)
+
+---
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/backup-partition.md b/doc/backup-partition.md
new file mode 100644
index 0000000..e2ca8e0
--- /dev/null
+++ b/doc/backup-partition.md
@@ -0,0 +1,61 @@
+Save configuration to fallback partition
+========================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script saves the current configuration to fallback
+[partition](https://wiki.mikrotik.com/wiki/Manual:Partitions).
+
+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**: Only the configuration is saved to backup partition.
+> Every now and then you should copy your installation over for a recent
+> RouterOS version!
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+ $ScriptInstallUpdate backup-partition;
+
+Usage and invocation
+--------------------
+
+Just run the script:
+
+ /system/script/run backup-partition;
+
+Creating a scheduler may be an option:
+
+ /system/scheduler/add interval=1w name=backup-partition on-event="/system/script/run backup-partition;" start-time=09:30:00;
+
+See also
+--------
+
+* [Upload backup to Mikrotik cloud](backup-cloud.md)
+* [Send backup via e-mail](backup-email.md)
+* [Upload backup to server](backup-upload.md)
+* [Forward log messages via notification](log-forward.md)
+
+---
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/backup-upload.d/notification.avif b/doc/backup-upload.d/notification.avif
new file mode 100644
index 0000000..83cfb18
--- /dev/null
+++ b/doc/backup-upload.d/notification.avif
Binary files differ
diff --git a/doc/backup-upload.md b/doc/backup-upload.md
new file mode 100644
index 0000000..953ac93
--- /dev/null
+++ b/doc/backup-upload.md
@@ -0,0 +1,92 @@
+Upload backup to server
+=======================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script uploads binary backup (`/system/backup/save`) and complete
+configuration export (`/export terse show-sensitive`) to external server.
+
+> ⚠️ **Warning**: The used command can hit errors that a script can not handle.
+> This may result in script termination (where no notification is sent) or
+> malfunction of fetch command (where all up- and downloads break) for some
+> time. Failed notifications are queued then.
+
+### Sample notification
+
+![backup-upload notification](backup-upload.d/notification.avif)
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+ $ScriptInstallUpdate backup-upload;
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `BackupSendBinary`: whether to send binary backup
+* `BackupSendExport`: whether to send configuration export
+* `BackupSendGlobalConfig`: whether to send `global-config-overlay`
+* `BackupPassword`: password to encrypt the backup with
+* `BackupRandomDelay`: delay up to amount of seconds when run from scheduler
+* `BackupUploadUrl`: url to upload to
+* `BackupUploadUser`: username for server authentication
+* `BackupUploadPass`: password for server authentication
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+### Issues with SFTP client
+
+The RouterOS SFTP client is picky if it comes to authentication methods.
+I had to disable all but password authentication on server side. For openssh
+edit `/etc/ssh/sshd_config` and add a directive like this, changed for your
+needs:
+
+ Match User mikrotik
+ AuthenticationMethods password
+
+Usage and invocation
+--------------------
+
+Just run the script:
+
+ /system/script/run backup-upload;
+
+Creating a scheduler may be an option:
+
+ /system/scheduler/add interval=1w name=backup-upload on-event="/system/script/run backup-upload;" start-time=09:25:00;
+
+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)
+
+---
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/bridge-port.md b/doc/bridge-port.md
deleted file mode 100644
index 8cbe15b..0000000
--- a/doc/bridge-port.md
+++ /dev/null
@@ -1,78 +0,0 @@
-Manage ports in bridge
-======================
-
-[◀ Go back to main README](../README.md)
-
-Description
------------
-
-These scripts are supposed to handle interfaces and switching them from
-one bridge to another.
-
-Requirements and installation
------------------------------
-
-Just install the scripts:
-
- $ScriptInstallUpdate bridge-port-to-default,bridge-port-toggle;
-
-Configuration
--------------
-
-The configuration goes to ports' comments (`/ interface bridge port`).
-
- / interface bridge port add bridge=br-guest comment="default=dhcp-client, alt=br-guest" disabled=yes interface=en1;
- / interface bridge port add bridge=br-intern comment="default=br-intern, alt=br-guest" interface=en2;
- / interface bridge port add bridge=br-guest comment="default=br-guest, extra=br-extra" interface=en3;
-
-Also dhcp client can be handled:
-
- / ip dhcp-client add comment="toggle with bridge port" disabled=no interface=en1;
-
-There is also global configuration:
-
-* `BridgePortTo`: specify the configuration to be applied by default
-
-Add a scheduler to start with default setup on system startup:
-
- / system scheduler add name=bridge-port-to-default on-event="/ system script run bridge-port-to-default;" start-time=startup;
-
-Usage and invocation
---------------------
-
-The usage examples show what happens with the configuration from above.
-
-Running the script `bridge-port-to-default` applies all configuration given
-with `default=`:
-
- / system script run bridge-port-to-default;
-
-For the three interfaces we get this configuration:
-
-* The special value `dhcp-client` enables the dhcp client for interface `en1`. The bridge port entry is disabled.
-* Interface `en2` is put in bridge `br-intern`.
-* Interface `en3` is put in bridge `br-guest`.
-
-Running the script `bridge-port-toggle` toggles to configuration given
-with `alt=`:
-
- / system script run bridge-port-toggle;
-
-* Interface `en1` is put in bridge `br-guest`, dhcp client for the interface is disabled.
-* Interface `en2` is put in bridge `br-guest`.
-* Interface `en3` is unchanged, stays in bridge `br-guest`.
-
-Running the script `bridge-port-toggle` again toggles back to configuration
-given with `default=`.
-
-More configuration can be loaded by setting `BridgePortTo`:
-
- :set BridgePortTo "extra";
- / system script run bridge-port-to-default;
-
-* Interfaces `en1` and `en2` are unchanged.
-* Interface `en3` is put in bridge `br-intern`.
-
----
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
diff --git a/doc/capsman-download-packages.md b/doc/capsman-download-packages.md
index 8516ff4..20fb007 100644
--- a/doc/capsman-download-packages.md
+++ b/doc/capsman-download-packages.md
@@ -1,7 +1,17 @@
Download packages for CAP upgrade from CAPsMAN
=============================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -15,33 +25,47 @@ This script automatically downloads these packages.
Requirements and installation
-----------------------------
-Just install the script on CAPsMAN device:
+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 `wifi`:
+
+ $ScriptInstallUpdate capsman-download-packages.wifi;
+
+For legacy CAPsMAN:
+
+ $ScriptInstallUpdate capsman-download-packages.capsman;
+
+Optionally add a scheduler to run after startup. For `wifi`:
+
+ /system/scheduler/add name=capsman-download-packages on-event="/system/script/run capsman-download-packages.wifi;" start-time=startup;
+
+For legacy CAPsMAN:
- $ScriptInstallUpdate capsman-download-packages;
+ /system/scheduler/add name=capsman-download-packages on-event="/system/script/run capsman-download-packages.capsman;" start-time=startup;
-Optionally add a scheduler to run after startup:
+Packages available in local storage in older version are downloaded
+unconditionally.
- / system scheduler add name=capsman-download-packages on-event="/ system script run capsman-download-packages;" start-time=startup;
+If no packages are found the script downloads a default set of packages:
-Only packages available in older version are downloaded. For initial setup
-place the required packages to CAPsMAN package path (see
-`/ caps-man manager`). The packages can be downloaded from device with
-function `$DownloadPackage`, use something like this to download latest
-packages to directory `routeros`:
+ * `wifi`: `routeros` and `wifi-qcom` for *arm* and *arm64*, `wifi-qcom-ac` for *arm*
+ * legacy CAPsMAN: `routeros` and `wireless` for *arm* and *mipsbe*
- $DownloadPackage system "" arm routeros;
- $DownloadPackage security "" arm routeros;
- [...]
- $DownloadPackage system "" mipsbe routeros;
- $DownloadPackage security "" mipsbe routeros;
- [...]
+> ℹ️ **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;
+ /system/script/run capsman-download-packages.wifi;
... or from scheduler.
@@ -55,5 +79,5 @@ See also
* [Run rolling CAP upgrades from CAPsMAN](capsman-rolling-upgrade.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/capsman-rolling-upgrade.md b/doc/capsman-rolling-upgrade.md
index 04952e2..8362794 100644
--- a/doc/capsman-rolling-upgrade.md
+++ b/doc/capsman-rolling-upgrade.md
@@ -1,7 +1,17 @@
Run rolling CAP upgrades from CAPsMAN
=====================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -17,9 +27,17 @@ parallel.
Requirements and installation
-----------------------------
-Just install the 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 `wifi`:
+
+ $ScriptInstallUpdate capsman-rolling-upgrade.wifi;
+
+For legacy CAPsMAN:
- $ScriptInstallUpdate capsman-rolling-upgrade;
+ $ScriptInstallUpdate capsman-rolling-upgrade.capsman;
Usage and invocation
--------------------
@@ -30,7 +48,7 @@ that script when required.
Alternatively run it manually:
- / system script run capsman-rolling-upgrade;
+ /system/script/run capsman-rolling-upgrade.wifi;
See also
--------
@@ -38,5 +56,5 @@ See also
* [Download packages for CAP upgrade from CAPsMAN](capsman-download-packages.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/certificate-renew-issued.md b/doc/certificate-renew-issued.md
index e460ce1..2df8be3 100644
--- a/doc/certificate-renew-issued.md
+++ b/doc/certificate-renew-issued.md
@@ -1,7 +1,17 @@
Renew locally issued certificates
=================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -25,12 +35,16 @@ parameter:
* `CertRenewPass`: an array holding individual passphrases for certificates
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
Usage and invocation
--------------------
Run the script to renew certificates issued from a local CA.
- / system script run certificate-renew-issued;
+ /system/script/run certificate-renew-issued;
Only scripts with a remaining lifetime of three weeks or less are renewed.
The old certificate is revoked automatically. If a passphrase for a specific
@@ -43,5 +57,5 @@ See also
* [Renew certificates and notify on expiration](check-certificates.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/check-certificates.d/notification.avif b/doc/check-certificates.d/notification.avif
new file mode 100644
index 0000000..7c250da
--- /dev/null
+++ b/doc/check-certificates.d/notification.avif
Binary files differ
diff --git a/doc/check-certificates.md b/doc/check-certificates.md
index 318805d..62b9ceb 100644
--- a/doc/check-certificates.md
+++ b/doc/check-certificates.md
@@ -1,7 +1,17 @@
Renew certificates and notify on expiration
===========================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -9,6 +19,10 @@ Description
This script tries to download and renew certificates, then notifies about
certificates that are still about to expire.
+### Sample notification
+
+![check-certificates notification](check-certificates.d/notification.avif)
+
Requirements and installation
-----------------------------
@@ -19,32 +33,59 @@ Just install the script:
Configuration
-------------
-The expiry notifications just require notification settings for e-mail and
-telegram.
-
For automatic download and renewal of certificates you need configuration
in `global-config-overlay`, these are the parameters:
* `CertRenewPass`: an array of passphrases to try
+* `CertRenewTime`: on what remaining time to try a renew
* `CertRenewUrl`: the url to download certificates from
+* `CertWarnTime`: on what remaining time to warn via notification
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Certificates on the web server should be named by their common name, like
+`CN.pem` (`PEM` format) or`CN.p12` (`PKCS#12` format). Alternatively any
+subject alternative name (aka *Subject Alt Name* or *SAN*) can be used.
-Certificates on the web server should be named `CN.pem` (`PEM` format) or
-`CN.p12` (`PKCS#12` format).
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
Usage and invocation
--------------------
Just run the script:
- / system script run check-certificates;
+ /system/script/run check-certificates;
... or create a scheduler for periodic execution:
- / system scheduler add interval=1d name=check-certificates on-event="/ system script run check-certificates;" start-time=startup;
+ /system/scheduler/add interval=1d name=check-certificates on-event="/system/script/run check-certificates;" start-time=startup;
+
+
+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
-Alternatively running on startup may be desired:
+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:
- / system scheduler add name=check-certificates-startup on-event="/ system script run check-certificates;" start-time=startup;
+ /certificate/add name=example.com common-name=example.com days-valid=1;
+ /certificate/sign example.com;
+ /system/script/run check-certificates;
See also
--------
@@ -52,5 +93,5 @@ See also
* [Renew locally issued certificates](certificate-renew-issued.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/check-health.d/notification-01-cpu-utilization-high.avif b/doc/check-health.d/notification-01-cpu-utilization-high.avif
new file mode 100644
index 0000000..326e7fe
--- /dev/null
+++ b/doc/check-health.d/notification-01-cpu-utilization-high.avif
Binary files differ
diff --git a/doc/check-health.d/notification-02-cpu-utilization-ok.avif b/doc/check-health.d/notification-02-cpu-utilization-ok.avif
new file mode 100644
index 0000000..811ccd7
--- /dev/null
+++ b/doc/check-health.d/notification-02-cpu-utilization-ok.avif
Binary files differ
diff --git a/doc/check-health.d/notification-03-ram-utilization-high.avif b/doc/check-health.d/notification-03-ram-utilization-high.avif
new file mode 100644
index 0000000..59155c5
--- /dev/null
+++ b/doc/check-health.d/notification-03-ram-utilization-high.avif
Binary files differ
diff --git a/doc/check-health.d/notification-04-ram-utilization-ok.avif b/doc/check-health.d/notification-04-ram-utilization-ok.avif
new file mode 100644
index 0000000..d995b9a
--- /dev/null
+++ b/doc/check-health.d/notification-04-ram-utilization-ok.avif
Binary files differ
diff --git a/doc/check-health.d/notification-05-voltage.avif b/doc/check-health.d/notification-05-voltage.avif
new file mode 100644
index 0000000..17a385b
--- /dev/null
+++ b/doc/check-health.d/notification-05-voltage.avif
Binary files differ
diff --git a/doc/check-health.d/notification-06-temperature-high.avif b/doc/check-health.d/notification-06-temperature-high.avif
new file mode 100644
index 0000000..60d3802
--- /dev/null
+++ b/doc/check-health.d/notification-06-temperature-high.avif
Binary files differ
diff --git a/doc/check-health.d/notification-07-temperature-ok.avif b/doc/check-health.d/notification-07-temperature-ok.avif
new file mode 100644
index 0000000..4afed02
--- /dev/null
+++ b/doc/check-health.d/notification-07-temperature-ok.avif
Binary files differ
diff --git a/doc/check-health.d/notification-08-psu-fail.avif b/doc/check-health.d/notification-08-psu-fail.avif
new file mode 100644
index 0000000..ad049ac
--- /dev/null
+++ b/doc/check-health.d/notification-08-psu-fail.avif
Binary files differ
diff --git a/doc/check-health.d/notification-09-psu-ok.avif b/doc/check-health.d/notification-09-psu-ok.avif
new file mode 100644
index 0000000..26f5a74
--- /dev/null
+++ b/doc/check-health.d/notification-09-psu-ok.avif
Binary files differ
diff --git a/doc/check-health.md b/doc/check-health.md
index 37f8e34..ee52b61 100644
--- a/doc/check-health.md
+++ b/doc/check-health.md
@@ -1,7 +1,17 @@
Notify about health state
=========================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -9,16 +19,46 @@ Description
This script is run from scheduler periodically, sending notification on
health related events:
+* high CPU utilization
+* high RAM utilization (low available RAM)
* voltage jumps up or down more than configured threshold
+* voltage drops below hard lower limit
* power supply failed or recovered
* temperature is above or below threshold
Note that bad initial state will not trigger an event.
-Only sensors available in hardware can be checked. See what your
-hardware supports:
+Monitoring CPU and RAM utilization (available processing and memory
+resources) works on all devices. Other than that only sensors available
+in hardware can be checked. See what your hardware supports:
+
+ /system/health/print;
+
+### Sample notifications
+
+#### CPU utilization
+
+![check-health notification cpu utilization high](check-health.d/notification-01-cpu-utilization-high.avif)
+![check-health notification cpu utilization ok](check-health.d/notification-02-cpu-utilization-ok.avif)
+
+#### RAM utilization (low available RAM)
+
+![check-health notification ram utilization high](check-health.d/notification-03-ram-utilization-high.avif)
+![check-health notification ram utilization ok](check-health.d/notification-04-ram-utilization-ok.avif)
- / system health print;
+#### Voltage
+
+![check-health notification voltage](check-health.d/notification-05-voltage.avif)
+
+#### Temperature
+
+![check-health notification temperature high](check-health.d/notification-06-temperature-high.avif)
+![check-health notification temperature ok](check-health.d/notification-07-temperature-ok.avif)
+
+#### PSU state
+
+![check-health notification psu fail](check-health.d/notification-08-psu-fail.avif)
+![check-health notification psu ok](check-health.d/notification-09-psu-ok.avif)
Requirements and installation
-----------------------------
@@ -26,18 +66,31 @@ Requirements and installation
Just install the script and create a scheduler:
$ScriptInstallUpdate check-health;
- / system scheduler add interval=1m name=check-health on-event="/ system script run check-health;" start-time=startup;
+ /system/scheduler/add interval=53s name=check-health on-event="/system/script/run check-health;" start-time=startup;
+
+> ℹ️ **Info**: Running lots of scripts simultaneously can tamper the
+> precision of cpu utilization, escpecially on devices with limited
+> resources. Thus an unusual interval is used here.
Configuration
-------------
-The configuration goes to `global-config-overlay`, These are the parameters:
+The configuration goes to `global-config-overlay`, these are the parameters:
* `CheckHealthTemperature`: an array specifying temperature thresholds for sensors
+* `CheckHealthVoltageLow`: value (in volt*10) giving a hard lower limit
* `CheckHealthVoltagePercent`: percentage value to trigger voltage jumps
-Also notification settings are required for e-mail and telegram.
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/check-lte-firmware-upgrade.d/notification.avif b/doc/check-lte-firmware-upgrade.d/notification.avif
new file mode 100644
index 0000000..c440da5
--- /dev/null
+++ b/doc/check-lte-firmware-upgrade.d/notification.avif
Binary files differ
diff --git a/doc/check-lte-firmware-upgrade.md b/doc/check-lte-firmware-upgrade.md
index c71dee5..bec3177 100644
--- a/doc/check-lte-firmware-upgrade.md
+++ b/doc/check-lte-firmware-upgrade.md
@@ -1,7 +1,17 @@
Notify on LTE firmware upgrade
==============================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -14,6 +24,10 @@ upgrades. Currently supported LTE hardware:
* R11e-4G
* R11e-LTE6
+### Sample notification
+
+![check-lte-firmware-upgrade notification](check-lte-firmware-upgrade.d/notification.avif)
+
Requirements and installation
-----------------------------
@@ -23,12 +37,15 @@ Just install the script:
... and create a scheduler:
- / system scheduler add interval=1d name=check-lte-firmware-upgrade on-event="/ system script run check-lte-firmware-upgrade;" start-time=startup;
+ /system/scheduler/add interval=1d name=check-lte-firmware-upgrade on-event="/system/script/run check-lte-firmware-upgrade;" start-time=startup;
Configuration
-------------
-Notification setting are required for e-mail and telegram.
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[matrix](mod/notification-matrix.md) and/or
+[telegram](mod/notification-telegram.md).
See also
--------
@@ -37,5 +54,5 @@ See also
* [Install LTE firmware upgrade](unattended-lte-firmware-upgrade.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/check-routeros-update.d/notification.avif b/doc/check-routeros-update.d/notification.avif
new file mode 100644
index 0000000..50317cf
--- /dev/null
+++ b/doc/check-routeros-update.d/notification.avif
Binary files differ
diff --git a/doc/check-routeros-update.md b/doc/check-routeros-update.md
index bd25ab1..dbb2b89 100644
--- a/doc/check-routeros-update.md
+++ b/doc/check-routeros-update.md
@@ -1,7 +1,17 @@
Notify on RouterOS update
=========================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -11,11 +21,24 @@ The primary use of this script is to notify about RouterOS updates.
Run from a terminal you can start the update process or schedule it.
Centrally managing update process of several devices is possibly by
-specifying versions safe to be updated on a web server.
+specifying versions safe to be updated on a web server. Versions seen
+in neighbor discovery can be specified to be safe as well.
Also installing patch updates (where just last digit is increased)
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
+> to your production environment! Automatic updates should be handled
+> with care!
+
+### Sample notification
+
+![check-routeros-update notification](check-routeros-update.d/notification.avif)
+
Requirements and installation
-----------------------------
@@ -25,36 +48,60 @@ Just install the script:
And add a scheduler for automatic update notification:
- / system scheduler add interval=1d name=check-routeros-update on-event="/ system script run check-routeros-update;" start-time=startup;
+ /system/scheduler/add interval=1d name=check-routeros-update on-event="/system/script/run check-routeros-update;" start-time=startup;
Configuration
-------------
-Configuration is required only if you want to control update process with
-safe versions from a web server. The configuration goes to
-`global-config-overlay`, this is the parameter:
-
-* `SafeUpdateNeighbor`: install updates automatically if seen in neighbor list
-* `SafeUpdatePatch`: install patch updates automatically
-* `SafeUpdateUrl`: url to check for safe update, the channel (`long-term`,
-`stable` or `testing`) is appended
+No extra configuration is required to receive notifications. Several
+mechanisms are availalbe to enable automatic installation of updates.
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `SafeUpdateNeighbor`: install updates automatically if at least one other
+ device is seen in neighbor list with new version
+* `SafeUpdateNeighborIdentity`: regular expression to match identity for
+ trusted devices, leave empty to match all
+* `SafeUpdatePatch`: install patch updates (where just last digit changes)
+ automatically
+* `SafeUpdateUrl`: url on webserver to check for safe update, the channel
+ (`long-term`, `stable` or `testing`) is appended
+* `SafeUpdateAll`: install **all** updates automatically
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
Usage and invocation
--------------------
Be notified when run from scheduler or run it manually:
- / system script run check-routeros-update;
+ /system/script/run check-routeros-update;
If an update is found you can install it right away.
Installing script [packages-update](packages-update.md) gives extra options.
+Tips & Tricks
+-------------
+
+The script checks for full connectivity before acting, so scheduling at
+startup is perfectly valid:
+
+ /system/scheduler/add name=check-routeros-update@startup on-event="/system/script/run check-routeros-update;" start-time=startup;
+
See also
--------
+* [Automatically upgrade firmware and reboot](firmware-upgrade-reboot.md)
* [Manage system update](packages-update.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/cloud-backup.md b/doc/cloud-backup.md
index 1417242..e161cfa 100644
--- a/doc/cloud-backup.md
+++ b/doc/cloud-backup.md
@@ -1,47 +1 @@
-Upload backup to Mikrotik cloud
-===============================
-
-[◀ Go back to main README](../README.md)
-
-Description
------------
-
-This script uploads [binary backup to Mikrotik cloud](https://wiki.mikrotik.com/wiki/Manual:IP/Cloud#Backup).
-
-Requirements and installation
------------------------------
-
-Just install the script:
-
- $ScriptInstallUpdate cloud-backup;
-
-Configuration
--------------
-
-The configuration goes to `global-config-overlay`, these are the parameters:
-
-* `BackupPassword`: password to encrypt the backup with
-* `BackupRandomDelay`: delay up to amount of seconds when run from scheduler
-
-Also notification settings are required for e-mail and telegram.
-
-Usage and invocation
---------------------
-
-Just run the script:
-
- / system script run cloud-backup;
-
-Creating a scheduler may be an option:
-
- / system scheduler add interval=1w name=cloud-backup on-event="/ system script run cloud-backup;" start-time=09:20:00;
-
-See also
---------
-
-* [Send backup via e-mail](email-backup.md)
-* [Upload backup to server](upload-backup.md)
-
----
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+This script has been renamed. Please see [backup-cloud](backup-cloud.md).
diff --git a/doc/collect-wireless-mac.d/notification.avif b/doc/collect-wireless-mac.d/notification.avif
new file mode 100644
index 0000000..a2833f0
--- /dev/null
+++ b/doc/collect-wireless-mac.d/notification.avif
Binary files differ
diff --git a/doc/collect-wireless-mac.md b/doc/collect-wireless-mac.md
index 45489bf..b0a2298 100644
--- a/doc/collect-wireless-mac.md
+++ b/doc/collect-wireless-mac.md
@@ -1,7 +1,17 @@
Collect MAC addresses in wireless access list
=============================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -12,17 +22,26 @@ address list. In addition a notification is sent.
By default the access list entry is disabled, but you can easily enable
and modify it to your needs.
+### Sample notification
+
+![collect-wireless-mac notification](collect-wireless-mac.d/notification.avif)
+
Requirements and installation
-----------------------------
-Depending on whether you use CAPsMAN (`/ caps-man`) or local wireless
-interface (`/ interface wireless`) you need to install a different script.
+Depending on whether you use `wifi` package (`/interface/wifi`), legacy
+wifi with CAPsMAN (`/caps-man`) or local wireless interface
+(`/interface/wireless`) you need to install a different script.
+
+For `wifi`:
+
+ $ScriptInstallUpdate collect-wireless-mac.wifi;
-For CAPsMAN:
+For legacy CAPsMAN:
$ScriptInstallUpdate collect-wireless-mac.capsman;
-For local interface:
+For legacy local interface:
$ScriptInstallUpdate collect-wireless-mac.local;
@@ -33,7 +52,11 @@ On first run a disabled access list entry acting as marker (with comment
"`--- collected above ---`") is added. Move this entry to define where new
entries are to be added.
-Also notification settings are required for e-mail and telegram.
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
Usage and invocation
--------------------
@@ -50,5 +73,5 @@ See also
* [Run other scripts on DHCP lease](lease-script.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/daily-psk.d/notification.avif b/doc/daily-psk.d/notification.avif
new file mode 100644
index 0000000..dd0b1b6
--- /dev/null
+++ b/doc/daily-psk.d/notification.avif
Binary files differ
diff --git a/doc/daily-psk.md b/doc/daily-psk.md
index d472269..f723617 100644
--- a/doc/daily-psk.md
+++ b/doc/daily-psk.md
@@ -1,7 +1,17 @@
Use wireless network with daily psk
===================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -9,26 +19,37 @@ Description
This script is supposed to provide a wifi network which changes the
passphrase to a pseudo-random string daily.
+### Sample notification
+
+![daily-psk notification](daily-psk.d/notification.avif)
+
Requirements and installation
-----------------------------
Just install this script.
-Depending on whether you use CAPsMAN (`/ caps-man`) or local wireless
-interface (`/ interface wireless`) you need to install a different script.
+Depending on whether you use `wifi` package (`/interface/wifi`), legacy
+wifi with CAPsMAN (`/caps-man`) or local wireless interface
+(`/interface/wireless`) you need to install a different script and add
+schedulers to run the script:
-For CAPsMAN:
+For `wifi`:
- $ScriptInstallUpdate daily-psk.capsman;
+ $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 local interface:
+For legacy CAPsMAN:
- $ScriptInstallUpdate daily-psk.local;
+ $ScriptInstallUpdate daily-psk.capsman;
+ /system/scheduler/add interval=1d name=daily-psk on-event="/system/script/run daily-psk.capsman;" start-time=03:00:00;
+ /system/scheduler/add name=daily-psk@startup on-event="/system/script/run daily-psk.capsman;" start-time=startup;
-And add schedulers to run the script:
+For legacy local interface:
- / system scheduler add interval=1d name=daily-psk-nightly on-event="/ system script run daily-psk.local;" start-date=may/23/2018 start-time=03:00:00;
- / system scheduler add name=daily-psk-startup on-event="/ system script run daily-psk.local;" start-time=startup;
+ $ScriptInstallUpdate daily-psk.local;
+ /system/scheduler/add interval=1d name=daily-psk on-event="/system/script/run daily-psk.local;" start-time=03:00:00;
+ /system/scheduler/add name=daily-psk@startup on-event="/system/script/run daily-psk.local;" start-time=startup;
These will update the passphrase on boot and nightly at 3:00.
@@ -40,12 +61,28 @@ The configuration goes to `global-config-overlay`, these are the parameters:
* `DailyPskMatchComment`: pattern to match the wireless access list comment
* `DailyPskSecrets`: an array with pseudo random strings
-Then add an access list entry:
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Then add an access list entry. For `wifi`:
+
+ /interface/wifi/access-list/add comment="Daily PSK" ssid-regexp="-guest\$" passphrase="ToBeChangedDaily";
+
+For legacy CAPsMAN:
+
+ /caps-man/access-list/add comment="Daily PSK" ssid-regexp="-guest\$" private-passphrase="ToBeChangedDaily";
+
+For legacy local interface:
- / interface wireless access-list add comment="Daily PSK" interface=wl-daily private-pre-shared-key="ToBeChangedDaily";
+ /interface/wireless/access-list/add comment="Daily PSK" interface=wl-daily private-pre-shared-key="ToBeChangedDaily";
-Also notification settings are required for e-mail and telegram.
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[trix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/dhcp-lease-comment.md b/doc/dhcp-lease-comment.md
index caba7d6..4831b8c 100644
--- a/doc/dhcp-lease-comment.md
+++ b/doc/dhcp-lease-comment.md
@@ -1,7 +1,17 @@
Comment DHCP leases with info from access list
==============================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -12,14 +22,19 @@ from wireless access list.
Requirements and installation
-----------------------------
-Depending on whether you use CAPsMAN (`/ caps-man`) or local wireless
-interface (`/ interface wireless`) you need to install a different script.
+Depending on whether you use `wifi` package (`/interface/wifi`), legacy
+wifi with CAPsMAN (`/caps-man`) or local wireless interface
+(`/interface/wireless`) you need to install a different script.
+
+For `wifi`:
+
+ $ScriptInstallUpdate dhcp-lease-comment.wifi;
-For CAPsMAN:
+For legacy CAPsMAN:
$ScriptInstallUpdate dhcp-lease-comment.capsman;
-For local interface:
+For legacy local interface:
$ScriptInstallUpdate dhcp-lease-comment.local;
@@ -45,5 +60,5 @@ See also
* [Run other scripts on DHCP lease](lease-script.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/dhcp-to-dns.md b/doc/dhcp-to-dns.md
index 699b6a5..e7f3b88 100644
--- a/doc/dhcp-to-dns.md
+++ b/doc/dhcp-to-dns.md
@@ -1,12 +1,24 @@
Create DNS records for DHCP leases
==================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
-This script adds (and removes) dns records based on dhcp server leases.
+This script adds (and updates & removes) dns records based on dhcp server
+leases. An A record based on mac address is created for all bound lease,
+additionally a CNAME record is created from host name if available.
Requirements and installation
-----------------------------
@@ -20,7 +32,7 @@ Then run it from dhcp server as lease script. You may want to use
A scheduler cares about cleanup:
- / system scheduler add interval=15m name=dhcp-to-dns on-event="/ system script run dhcp-to-dns;" start-time=startup;
+ /system/scheduler/add interval=15m name=dhcp-to-dns on-event="/system/script/run dhcp-to-dns;" start-time=startup;
Configuration
-------------
@@ -29,20 +41,53 @@ On first run a disabled static dns record acting as marker (with comment
"`--- dhcp-to-dns above ---`") is added. Move this entry to define where new
entries are to be added.
-The configuration goes to `global-config-overlay`, these are the parameters:
+The configuration goes to dhcp server's network definition. The domain is
+used to form the dns name:
+
+ /ip/dhcp-server/network/add address=10.0.0.0/24 domain=example.com;
+
+A bound lease for mac address `00:11:22:33:44:55` with ip address
+`10.0.0.50` would result in an A record `00-11-22-33-44-55.example.com`
+pointing to the given ip address.
+
+Additional options can be given from comment, to add an extra level in
+dns name or define a different domain.
+
+ /ip/dhcp-server/network/add address=10.0.0.0/24 domain=example.com comment="domain=another-domain.com, name-extra=dhcp";
+
+This example would result in name `00-11-22-33-44-55.dhcp.another-domain.com`
+for the same lease.
+
+If no domain is found in dhcp server's network definition a fallback from
+`global-config-overlay` is used. This is the parameter:
* `Domain`: the domain used for dns records
-* `HostNameInZone`: whether or not to add the dhcp/dns server's hostname
-* `PrefixInZone`: whether or not to add prefix `dhcp`
-* `ServerNameInZone`: whether or not to add DHCP server name
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+### Host name from DHCP lease comment
+
+Overwriting the host name from dhcp lease comment is supported, just add
+something like `hostname=new-hostname` in comment, and separate it by comma
+from other information if required:
+
+ /ip/dhcp-server/lease/add address=10.0.0.50 comment="my device, hostname=new-hostname" mac-address=00:11:22:33:44:55 server=dhcp;
+
+Note this information can be configured in wireless access list with
+[dhcp-lease-comment](dhcp-lease-comment.md), though it comes with a delay
+then due to script execution order. Decrease the scheduler interval to
+reduce the effect.
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 IPSec peers](ipsec-to-dns.md)
* [Run other scripts on DHCP lease](lease-script.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/early-errors.md b/doc/early-errors.md
index a16da7d..b3c6800 100644
--- a/doc/early-errors.md
+++ b/doc/early-errors.md
@@ -1,11 +1,2 @@
-Send notification with early errors
-===================================
-
-[◀ Go back to main README](../README.md)
-
-This script has been replace. Please migrate to
+This script has been replaced. Please migrate to
[Forward log messages via notification](log-forward.md).
-
----
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
diff --git a/doc/email-backup.md b/doc/email-backup.md
index 4b769ec..d674743 100644
--- a/doc/email-backup.md
+++ b/doc/email-backup.md
@@ -1,53 +1 @@
-Send backup via e-mail
-======================
-
-[◀ Go back to main README](../README.md)
-
-Description
------------
-
-This script sends binary backup (`/ system backup save`) and complete
-configuration export (`/ export terse`) via e-mail.
-
-
-Requirements and installation
------------------------------
-
-Just install the script:
-
- $ScriptInstallUpdate email-backup;
-
-Configuration
--------------
-
-The configuration goes to `global-config-overlay`, these are the parameters:
-
-* `BackupSendBinary`: whether to send binary backup
-* `BackupSendExport`: whether to send configuration export
-* `BackupPassword`: password to encrypt the backup with
-* `BackupRandomDelay`: delay up to amount of seconds when run from scheduler
-* `EmailBackupTo`: e-mail address to send to
-* `EmailBackupCc`: e-mail address(es) to send in copy
-
-Also valid e-mail settings in `/ tool e-mail` are required to send mails.
-
-Usage and invocation
---------------------
-
-Just run the script:
-
- / system script run email-backup;
-
-Creating a scheduler may be an option:
-
- / system scheduler add interval=1w name=email-backup on-event="/ system script run email-backup;" start-time=09:15:00;
-
-See also
---------
-
-* [Upload backup to Mikrotik cloud](cloud-backup.md)
-* [Upload backup to server](upload-backup.md)
-
----
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+This script has been renamed. Please see [backup-email](backup-email.md). \ No newline at end of file
diff --git a/doc/firmware-upgrade-reboot.md b/doc/firmware-upgrade-reboot.md
new file mode 100644
index 0000000..420dfe1
--- /dev/null
+++ b/doc/firmware-upgrade-reboot.md
@@ -0,0 +1,43 @@
+Automatically upgrade firmware and reboot
+=========================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+RouterOS and firmware are upgraded separately, activating the latter
+requires an extra reboot. This script handles upgrade and reboot.
+
+> ⚠️ **Warning**: This *should* be bullet proof, but I can not guarantee. In
+> worst case it has potential to cause a boot loop, so handle with care!
+
+Requirements and installation
+-----------------------------
+
+Just install the script and create a scheduler:
+
+ $ScriptInstallUpdate firmware-upgrade-reboot;
+ /system/scheduler/add name=firmware-upgrade-reboot on-event="/system/script/run firmware-upgrade-reboot;" start-time=startup;
+
+Enjoy firmware being up to date and in sync with RouterOS.
+
+See also
+--------
+
+* [Notify on RouterOS update](check-routeros-update.md)
+* [Manage system update](packages-update.md)
+
+---
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/fw-addr-lists.md b/doc/fw-addr-lists.md
new file mode 100644
index 0000000..70ca6e9
--- /dev/null
+++ b/doc/fw-addr-lists.md
@@ -0,0 +1,128 @@
+Download, import and update firewall address-lists
+==================================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script downloads, imports and updates firewall address-lists. Its main
+purpose is to block attacking ip addresses, spam hosts, command-and-control
+servers and similar malicious entities. The default configuration contains
+lists from [abuse.ch](https://abuse.ch/) and
+[dshield.org](https://dshield.org/), and
+lists from [spamhaus.org](https://spamhaus.org/) are prepared.
+
+The address-lists are updated in place, so after initial import you will not
+see situation when the lists are not populated.
+
+To mitigate man-in-the-middle attacks with altered lists the server's
+certificate is checked.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+ $ScriptInstallUpdate fw-addr-lists;
+
+And add two schedulers, first one for initial import after startup, second
+one for subsequent updates:
+
+ /system/scheduler/add name="fw-addr-lists@startup" start-time=startup on-event="/system/script/run fw-addr-lists;";
+ /system/scheduler/add name="fw-addr-lists" start-time=startup interval=2h on-event="/system/script/run fw-addr-lists;";
+
+> ℹ️ **Info**: Modify the interval to your needs, but it is recommended to
+> use less than half of the configured timeout for expiration.
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `FwAddrLists`: a list of firewall address-lists to download and import
+* `FwAddrListTimeOut`: the timeout for expiration without renew
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Naming a certificate for a list makes the script verify the server
+certificate, so you should add that if possible. Some certificates are
+available in my repository and downloaded automatically. Import it manually
+(menu `/certificate/`) if missing.
+
+Create firewall rules to process the packets that are related to addresses
+from address-lists.
+
+### IPv4 rules
+
+This rejects the packets from and to IPv4 addresses listed in
+address-list `block`.
+
+ /ip/firewall/filter/add chain=input src-address-list=block action=reject reject-with=icmp-admin-prohibited;
+ /ip/firewall/filter/add chain=forward src-address-list=block action=reject reject-with=icmp-admin-prohibited;
+ /ip/firewall/filter/add chain=forward dst-address-list=block action=reject reject-with=icmp-admin-prohibited;
+ /ip/firewall/filter/add chain=output dst-address-list=block action=reject reject-with=icmp-admin-prohibited;
+
+You may want to have an address-list to allow specific addresses, as prepared
+with a list `allow`. In fact you can use any list name, just change the
+default ones or add your own - matching in configuration and firewall rules.
+
+ /ip/firewall/filter/add chain=input src-address-list=allow action=accept;
+ /ip/firewall/filter/add chain=forward src-address-list=allow action=accept;
+ /ip/firewall/filter/add chain=forward dst-address-list=allow action=accept;
+ /ip/firewall/filter/add chain=output dst-address-list=allow action=accept;
+
+Modify these for your needs, but **most important**: Move the rules up in
+chains and make sure they actually take effect as expected!
+
+Alternatively handle the packets in firewall's raw section if you prefer:
+
+ /ip/firewall/raw/add chain=prerouting src-address-list=block action=drop;
+ /ip/firewall/raw/add chain=prerouting dst-address-list=block action=drop;
+ /ip/firewall/raw/add chain=output dst-address-list=block action=drop;
+
+> ⚠️ **Warning**: Just again... The order of firewall rules is important. Make
+> sure they actually take effect as expected!
+
+### IPv6 rules
+
+These are the same rules, but for IPv6.
+
+Reject packets in address-list `block`:
+
+ /ipv6/firewall/filter/add chain=input src-address-list=block action=reject reject-with=icmp-admin-prohibited;
+ /ipv6/firewall/filter/add chain=forward src-address-list=block action=reject reject-with=icmp-admin-prohibited;
+ /ipv6/firewall/filter/add chain=forward dst-address-list=block action=reject reject-with=icmp-admin-prohibited;
+ /ipv6/firewall/filter/add chain=output dst-address-list=block action=reject reject-with=icmp-admin-prohibited;
+
+Allow packets in address-list `allow`:
+
+ /ipv6/firewall/filter/add chain=input src-address-list=allow action=accept;
+ /ipv6/firewall/filter/add chain=forward src-address-list=allow action=accept;
+ /ipv6/firewall/filter/add chain=forward dst-address-list=allow action=accept;
+ /ipv6/firewall/filter/add chain=output dst-address-list=allow action=accept;
+
+Drop packets in firewall's raw section:
+
+ /ipv6/firewall/raw/add chain=prerouting src-address-list=block action=drop;
+ /ipv6/firewall/raw/add chain=prerouting dst-address-list=block action=drop;
+ /ipv6/firewall/raw/add chain=output dst-address-list=block action=drop;
+
+> ⚠️ **Warning**: Just again... The order of firewall rules is important. Make
+> sure they actually take effect as expected!
+
+---
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/global-wait.md b/doc/global-wait.md
new file mode 100644
index 0000000..a3fe6d6
--- /dev/null
+++ b/doc/global-wait.md
@@ -0,0 +1,47 @@
+Wait for global functions and modules
+=====================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+The global functions from `global-functions` and modules are loaded by
+scheduler at system startup. Running these functions at system startup may
+result in race condition where configuration and/or function are not yet
+available. This script is supposed to wait for everything being prepared.
+
+Do **not** add this script `global-wait` to the `global-scripts` scheduler!
+It would inhibit the initialization of configuration and functions.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+ $ScriptInstallUpdate global-wait;
+
+... and add it to your scheduler, for example in combination with the module
+to [manage VLANs on bridge ports](mod/bridge-port-vlan.md):
+
+ /system/scheduler/add name=bridge-port-vlan on-event="/system/script/run global-wait; :global BridgePortVlan; \$BridgePortVlan default;" start-time=startup;
+
+See also
+--------
+
+* [Manage ports in bridge](mod/bridge-port-to.md)
+* [Manage VLANs on bridge ports](mod/bridge-port-vlan.md)
+
+---
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/gps-track.md b/doc/gps-track.md
index cda7901..03f79a7 100644
--- a/doc/gps-track.md
+++ b/doc/gps-track.md
@@ -1,7 +1,17 @@
Send GPS position to server
===========================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -20,7 +30,7 @@ Just install the script:
... and create a scheduler:
- / system scheduler add interval=1m name=gps-track on-event="/ system script run gps-track;" start-time=startup;
+ /system/scheduler/add interval=1m name=gps-track on-event="/system/script/run gps-track;" start-time=startup;
Configuration
-------------
@@ -29,9 +39,13 @@ The configuration goes to `global-config-overlay`, the only parameter is:
* `GpsTrackUrl`: the url to send json data to
-The configured coordinate format (see `/ system gps`) defines the format
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+The configured coordinate format (see `/system/gps`) defines the format
sent to the server.
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/hotspot-to-wpa.md b/doc/hotspot-to-wpa.md
index fbb9640..6ce4421 100644
--- a/doc/hotspot-to-wpa.md
+++ b/doc/hotspot-to-wpa.md
@@ -1,28 +1,72 @@
-Use WPA2 network with hotspot credentials
-=========================================
+Use WPA network with hotspot credentials
+========================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
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:
+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 `wifi`:
+
+ $ScriptInstallUpdate hotspot-to-wpa.wifi;
+ /ip/hotspot/user/profile/set on-login="hotspot-to-wpa.wifi" [ find ];
+
+For legacy CAPsMAN:
+
+ $ScriptInstallUpdate hotspot-to-wpa.capsman;
+ /ip/hotspot/user/profile/set on-login="hotspot-to-wpa.capsman" [ find ];
+
+### Automatic cleanup
+
+With just `hotspot-to-wpa` installed the mac addresses will last in the
+access list forever. Install the optional script for automatic cleanup
+and add a scheduler.
- $ScriptInstallUpdate hotspot-to-wpa;
+For `wifi`:
-Configure your hotspot to use this script as `on-login` script:
+ $ScriptInstallUpdate hotspot-to-wpa-cleanup.wifi,lease-script;
+ /system/scheduler/add interval=1d name=hotspot-to-wpa-cleanup on-event="/system/script/run hotspot-to-wpa-cleanup.wifi;" start-time=startup;
- / ip hotspot user profile set on-login=hotspot-to-wpa [ find ];
+For legacy CAPsMAN:
+
+ $ScriptInstallUpdate hotspot-to-wpa-cleanup.capsman,lease-script;
+ /system/scheduler/add interval=1d name=hotspot-to-wpa-cleanup on-event="/system/script/run hotspot-to-wpa-cleanup.capsman;" start-time=startup;
+
+And add the lease script and matcher comment to your wpa interfaces' dhcp
+server. You can add more information to the comment, separated by comma. In
+this example the server is called `hotspot-to-wpa`.
+
+ /ip/dhcp-server/set lease-script=lease-script comment="hotspot-to-wpa=wpa" hotspot-to-wpa;
+
+You can specify the timeout after which a device is removed from leases and
+access-list. The default is four weeks.
+
+ /ip/dhcp-server/set lease-script=lease-script comment="hotspot-to-wpa=wpa, timeout=2w" hotspot-to-wpa;
Configuration
-------------
@@ -31,18 +75,50 @@ On first run a disabled access list entry acting as marker (with comment
"`--- hotspot-to-wpa above ---`") is added. Move this entry to define where new
entries are to be added.
-Usage and invocation
---------------------
-
Create hotspot login credentials:
- / ip hotspot user add add comment="Test User 1" name=user1 password=v3ry;
- / ip hotspot user add add comment="Test User 2" name=user2 password=s3cr3t;
+ /ip/hotspot/user/add comment="Test User 1" name=user1 password=v3ry;
+ /ip/hotspot/user/add comment="Test User 2" name=user2 password=s3cr3t;
+
+This also works with authentication via radius, but is limited then:
+Additional information is not available, including the password.
+
+Additionally templates can be created to give more options for access list:
+
+* `action`: set to `reject` to ignore logins on that hotspot
+* `passphrase` or `private-passphrase`: do **not** use passphrase from
+ hotspot's user credentials, but given one - or unset (use default
+ passphrase) with special word `ignore`
+* `ssid-regexp`: set a different SSID regular expression to match
+* `vlan-id`: connect device to specific VLAN
+* `vlan-mode`: set the VLAN mode for device
+
+For a hotspot called `example` the template could look like this.
+For `wifi`:
+
+ /interface/wifi/access-list/add comment="hotspot-to-wpa template example" disabled=yes passphrase="ignore" ssid-regexp="^example\$" vlan-id=10;
+
+For legacy CAPsMAN:
+
+ /caps-man/access-list/add comment="hotspot-to-wpa template example" disabled=yes private-passphrase="ignore" ssid-regexp="^example\$" vlan-id=10 vlan-mode=use-tag;
+
+The same settings are available in hotspot user's comment and take precedence
+over the template settings:
+
+ /ip/hotspot/user/add comment="private-passphrase=ignore, ssid-regexp=^example\\\$, vlan-id=10, vlan-mode=use-tag" name=user password=v3ry-s3cr3t;
+
+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
+--------
+
+* [Run other scripts on DHCP lease](lease-script.md)
+
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/ip-addr-bridge.md b/doc/ip-addr-bridge.md
index 44dac6a..8bb9811 100644
--- a/doc/ip-addr-bridge.md
+++ b/doc/ip-addr-bridge.md
@@ -1,7 +1,14 @@
Manage IP addresses with bridge status
======================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
Description
-----------
@@ -19,14 +26,14 @@ Just install the script:
... and make it run from scheduler:
- / system scheduler add name=ip-addr-bridge on-event="/ system script run ip-addr-bridge;" start-time=startup;
+ /system/scheduler/add name=ip-addr-bridge on-event="/system/script/run ip-addr-bridge;" start-time=startup;
-This will disable IP addresses on bridges without at lease one running port.
+This will disable IP addresses on bridges without at least one running port.
The IP address is enabled if at least one port is running.
Note that IP addresses on bridges without a single port (acting as loopback
interface) are ignored.
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/ipsec-to-dns.md b/doc/ipsec-to-dns.md
new file mode 100644
index 0000000..ca5b86c
--- /dev/null
+++ b/doc/ipsec-to-dns.md
@@ -0,0 +1,57 @@
+Create DNS records for IPSec peers
+==================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script adds (and removes) dns records based on IPSec peers and their
+dynamic addresses from mode-config.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+ $ScriptInstallUpdate ipsec-to-dns;
+
+This script is run from scheduler:
+
+ /system/scheduler/add interval=1m name=ipsec-to-dns on-event="/system/script/run ipsec-to-dns;" start-time=startup;
+
+Configuration
+-------------
+
+On first run a disabled static dns record acting as marker (with comment
+"`--- ipsec-to-dns above ---`") is added. Move this entry to define where new
+entries are to be added.
+
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `Domain`: the domain used for dns records
+* `HostNameInZone`: whether or not to add the ipsec/dns server's hostname
+* `PrefixInZone`: whether or not to add prefix `ipsec`
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+See also
+--------
+
+* [Create DNS records for DHCP leases](dns-to-dhcp.md)
+
+---
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/ipv6-update.md b/doc/ipv6-update.md
index f736433..a5661fc 100644
--- a/doc/ipv6-update.md
+++ b/doc/ipv6-update.md
@@ -1,14 +1,24 @@
Update configuration on IPv6 prefix change
==========================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
With changing IPv6 prefix from ISP this script handles to update...
-* ipv6 firewall address-list
+* ipv6 firewall address-list (prefixes (`/64`) and host addresses (`/128`))
* dns records
Requirements and installation
@@ -20,14 +30,14 @@ Just install the script:
Your ISP needs to provide an IPv6 prefix, your device receives it via dhcp:
- / ipv6 dhcp-client add add-default-route=yes interface=ppp-isp pool-name=isp request=prefix script=ipv6-update;
+ /ipv6/dhcp-client/add add-default-route=yes interface=ppp-isp pool-name=isp request=prefix script=ipv6-update;
Note this already adds this script as `script`. The pool name (here: "`isp`")
is important, we need it later.
Also this expects there is an address assigned from pool to an interface:
- / ipv6 address add from-pool=isp interface=br-local;
+ /ipv6/address/add from-pool=isp interface=br-local;
Sometimes dhcp client is stuck on reconnect and needs to be released.
Installing [ppp-on-up](ppp-on-up.md) may solve this.
@@ -38,7 +48,7 @@ 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:
- / ipv6 firewall address-list add address=2003:cf:2f0f:de00::/56 comment=ipv6-pool-isp list=extern;
+ /ipv6/firewall/address-list/add address=2003:cf:2f0f:de00::/56 comment=ipv6-pool-isp list=extern;
As this entry is mandatory it is created automatically if it does not exist,
with the comment also set for list.
@@ -47,13 +57,18 @@ Address list entries for specific interfaces can be updated as well. The
interface needs to get its address from pool `isp` and the address list entry
has to be associated to an interface in comment:
- / ipv6 firewall address-list add address=2003:cf:2f0f:de01::/64 comment="ipv6-pool-isp, interface=br-local" list=local;
+ /ipv6/firewall/address-list/add address=2003:cf:2f0f:de01::/64 comment="ipv6-pool-isp, interface=br-local" list=local;
+
+Updating address list entries with host addresses works as well, the new
+prefix is combinded with given suffix then:
+
+ /ipv6/firewall/address-list/add address=2003:cf:2f0f:de01:e3e0:f8fa:8cd6:dbe1/128 comment="ipv6-pool-isp, interface=br-local" list=hosts;
Static DNS records need a special comment to be updated. Again it has to
start with "`ipv6-pool-`" and actual pool name, followed by a comma,
"`interface=`" and the name of interface this address is connected to:
- / ip dns static add address=2003:cf:2f0f:de00:1122:3344:5566:7788 comment="ipv6-pool-isp, interface=br-local" name=test.example.com ttl=15m;
+ /ip/dns/static/add address=2003:cf:2f0f:de00:1122:3344:5566:7788 comment="ipv6-pool-isp, interface=br-local" name=test.example.com ttl=15m;
See also
--------
@@ -61,5 +76,5 @@ See also
* [Run scripts on ppp connection](ppp-on-up.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/lease-script.md b/doc/lease-script.md
index 3c774f1..f346621 100644
--- a/doc/lease-script.md
+++ b/doc/lease-script.md
@@ -1,17 +1,34 @@
Run other scripts on DHCP lease
===============================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
-This script is supposed to run from dhcp server as lease script. Currently
-it does:
+This script is supposed to run from dhcp server as lease script. On a dhcp
+lease it runs each script containing the following line, where `##` is a
+decimal number for ordering:
+
+ # provides: lease-script, order=##
+
+Currently it runs if available, in order:
-* run [dhcp-to-dns](dhcp-to-dns.md)
-* run [collect-wireless-mac](collect-wireless-mac.md)
-* run [dhcp-lease-comment](dhcp-lease-comment.md)
+* [dhcp-to-dns](dhcp-to-dns.md)
+* [collect-wireless-mac](collect-wireless-mac.md)
+* [dhcp-lease-comment](dhcp-lease-comment.md)
+* `hotspot-to-wpa-cleanup`, which is an optional cleanup script
+ of [hotspot-to-wpa](hotspot-to-wpa.md)
Requirements and installation
-----------------------------
@@ -22,7 +39,7 @@ Just install the script:
... and add it as `lease-script` to your dhcp server:
- / ip dhcp-server set lease-script=lease-script [ find ];
+ /ip/dhcp-server/set lease-script=lease-script [ find ];
See also
--------
@@ -30,7 +47,8 @@ See also
* [Collect MAC addresses in wireless access list](collect-wireless-mac.md)
* [Comment DHCP leases with info from access list](dhcp-lease-comment.md)
* [Create DNS records for DHCP leases](dhcp-to-dns.md)
+* [Use WPA network with hotspot credentials](hotspot-to-wpa.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/leds-mode.md b/doc/leds-mode.md
index b525220..5dd5f63 100644
--- a/doc/leds-mode.md
+++ b/doc/leds-mode.md
@@ -1,7 +1,14 @@
Manage LEDs dark mode
=====================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
Description
-----------
@@ -21,21 +28,21 @@ Usage and invocation
To switch the device to dark mode:
- / system script run leds-night-mode;
+ /system/script/run leds-night-mode;
... and back to normal mode:
- / system script run leds-day-mode;
+ /system/script/run leds-day-mode;
To toggle between the two modes:
- / system script run leds-toggle-mode;
+ /system/script/run leds-toggle-mode;
Add these schedulers to switch to dark mode in the evening and back to
normal mode in the morning:
- / system scheduler add interval=1d name=leds-day-mode on-event="/ system script run leds-day-mode;" start-time=07:00:00;
- / system scheduler add interval=1d name=leds-night-mode on-event="/ system script run leds-night-mode;" start-time=21:00:00;
+ /system/scheduler/add interval=1d name=leds-day-mode on-event="/system/script/run leds-day-mode;" start-time=07:00:00;
+ /system/scheduler/add interval=1d name=leds-night-mode on-event="/system/script/run leds-night-mode;" start-time=21:00:00;
The script `leds-toggle-mode` can be used from [mode button](mode-button.md)
to toggle mode.
@@ -46,5 +53,5 @@ See also
* [Mode button with multiple presses](mode-button.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/log-forward.d/notification.avif b/doc/log-forward.d/notification.avif
new file mode 100644
index 0000000..a0f9ab3
--- /dev/null
+++ b/doc/log-forward.d/notification.avif
Binary files differ
diff --git a/doc/log-forward.md b/doc/log-forward.md
index afb695e..4183d7b 100644
--- a/doc/log-forward.md
+++ b/doc/log-forward.md
@@ -1,21 +1,43 @@
Forward log messages via notification
=====================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
-RouterOS supports sending log messages via e-mail or to a syslog server.
-This has some limitation, however:
+RouterOS itself supports sending log messages via e-mail or to a syslog
+server (see `/system/logging`). This has some limitation, however:
* does not work early after boot if network connectivity is not
- yet established
+ yet established, or breaks intermittently
* lots of messages generate a flood of mails
-* Telegram is not supported
+* Matrix and Telegram are not supported
+
+The script works around the limitations, for example it does:
+
+* read from `/log`, including messages from early boot
+* skip multi-repeated messages
+* rate-limit itself to mitigate flooding
+* forward via notification (which includes *e-mail*, *Matrix* and *Telegram*
+ when installed and configured, see below)
-The script is intended to be run periodically. It collects log messages
-and forwards them via notification.
+It is intended to be run periodically from scheduler, then collects new
+log messages and forwards them via notification.
+
+### Sample notification
+
+![log-forward notification](log-forward.d/notification.avif)
Requirements and installation
-----------------------------
@@ -26,7 +48,7 @@ Just install the script:
... and add a scheduler:
- / system scheduler add interval=1m name=log-forward on-event="/ system script run log-forward;" start-time=startup;
+ /system/scheduler/add interval=1m name=log-forward on-event="/system/script/run log-forward;" start-time=startup;
Configuration
-------------
@@ -35,9 +57,39 @@ The configuration goes to `global-config-overlay`, these are the parameters:
* `LogForwardFilter`: define topics *not* to be forwarded
* `LogForwardFilterMessage`: define message text *not* to be forwarded
+* `LogForwardInclude`: define topics to be forwarded (even if filter matches)
+* `LogForwardIncludeMessage`: define message text to be forwarded (even if
+ filter matches)
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+These patterns are matched as
+[regular expressions](https://wiki.mikrotik.com/wiki/Manual:Regular_Expressions).
+To forward **all** (ignoring severity) log messages with topics `account`
+(which includes user logins) and `dhcp` you need something like:
+
+ :global LogForwardInclude "(account|dhcp)";
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+Tips & Tricks
+-------------
+
+### Notification on reboot
+
+You want to receive a notification on every device (re-)boot? Quite easy,
+just add:
+
+ :global LogForwardIncludeMessage "(^router rebooted)";
-Also notification settings are required for e-mail and telegram.
+This will match on every log message beginning with `router rebooted`.
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/mod/bridge-port-to.md b/doc/mod/bridge-port-to.md
new file mode 100644
index 0000000..838d1e0
--- /dev/null
+++ b/doc/mod/bridge-port-to.md
@@ -0,0 +1,88 @@
+Manage ports in bridge
+======================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module and its functio are are supposed to handle interfaces and
+switching them from one bridge to another.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+ $ScriptInstallUpdate mod/bridge-port-to;
+
+Configuration
+-------------
+
+The configuration goes to ports' comments (`/interface/bridge/port`).
+
+ /interface/bridge/port/add bridge=br-guest comment="default=dhcp-client, alt=br-guest" disabled=yes interface=en1;
+ /interface/bridge/port/add bridge=br-intern comment="default=br-intern, alt=br-guest" interface=en2;
+ /interface/bridge/port/add bridge=br-guest comment="default=br-guest, extra=br-extra" interface=en3;
+
+Also dhcp client can be handled:
+
+ /ip/dhcp-client/add comment="toggle with bridge port" disabled=no interface=en1;
+
+Add a scheduler to start with default setup on system startup:
+
+ $ScriptInstallUpdate global-wait;
+ /system/scheduler/add name=bridge-port-to on-event="/system/script/run global-wait; :global BridgePortTo; \$BridgePortTo default;" start-time=startup;
+
+Usage and invocation
+--------------------
+
+The usage examples show what happens with the configuration from above.
+
+Running the function `$BridgePortTo` with parameter `default` applies all
+configuration given with `default=`:
+
+ $BridgePortTo default;
+
+For the three interfaces we get this configuration:
+
+* The special value `dhcp-client` enables the dhcp client for interface `en1`. The bridge port entry is disabled.
+* Interface `en2` is put in bridge `br-intern`.
+* Interface `en3` is put in bridge `br-guest`.
+
+Running the function `$BridgePortTo` with parameter `alt` applies all
+configuration given with `alt=`:
+
+ $BridgePortTo alt;
+
+* Interface `en1` is put in bridge `br-guest`, dhcp client for the interface is disabled.
+* Interface `en2` is put in bridge `br-guest`.
+* Interface `en3` is unchanged, stays in bridge `br-guest`.
+
+Running the function `$BridgePortTo` with parameter `extra` applies another
+configuration:
+
+ $BridgePortTo extra;
+
+* Interfaces `en1` and `en2` are unchanged.
+* Interface `en3` is put in bridge `br-intern`.
+
+See also
+--------
+
+* [Wait for global functions und modules](../global-wait.md)
+* [Manage VLANs on bridge ports](bridge-port-vlan.md)
+
+---
+[⬅️ Go back to main README](../../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/mod/bridge-port-vlan.md b/doc/mod/bridge-port-vlan.md
new file mode 100644
index 0000000..5660b9d
--- /dev/null
+++ b/doc/mod/bridge-port-vlan.md
@@ -0,0 +1,92 @@
+Manage VLANs on bridge ports
+============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module and its function are supposed to handle VLANs on bridge ports.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+ $ScriptInstallUpdate mod/bridge-port-vlan;
+
+Configuration
+-------------
+
+Using named VLANs you have to add comments in bridge vlan menu:
+
+ /interface/bridge/vlan/add bridge=bridge comment=intern tagged=br-local vlan-ids=10;
+ /interface/bridge/vlan/add bridge=bridge comment=geust tagged=br-local vlan-ids=20;
+ /interface/bridge/vlan/add bridge=bridge comment=extra tagged=br-local vlan-ids=30;
+
+The configuration goes to ports' comments (`/interface/bridge/port`).
+
+ /interface/bridge/port/add bridge=bridge comment="default=dhcp-client, alt=guest" disabled=yes interface=en1;
+ /interface/bridge/port/add bridge=bridge comment="default=intern, alt=guest, extra=30" interface=en2;
+ /interface/bridge/port/add bridge=bridge comment="default=guest, extra=extra" interface=en3;
+
+Also dhcp client can be handled:
+
+ /ip/dhcp-client/add comment="toggle with bridge port" disabled=no interface=en1;
+
+Add a scheduler to start with default setup on system startup:
+
+ $ScriptInstallUpdate global-wait;
+ /system/scheduler/add name=bridge-port-vlan on-event="/system/script/run global-wait; :global BridgePortVlan; \$BridgePortVlan default;" start-time=startup;
+
+Usage and invocation
+--------------------
+
+The usage examples show what happens with the configuration from above.
+
+Running the function `$BridgePortVlan` with parameter `default` applies all
+configuration given with `default=`:
+
+ $BridgePortVlan default;
+
+For the three interfaces we get this configuration:
+
+* The special value `dhcp-client` enables the dhcp client for interface `en1`. The bridge port entry is disabled.
+* Primary VLAN `intern` (ID `10`) is configured on `en2`.
+* Primary VLAN `guest` (ID `20`) is configured on `en3`.
+
+Running the function `$BridgePortVlan` with parameter `alt` applies all
+configuration given with `alt=`:
+
+ $BridgePortVlan alt;
+
+* Primary VLAN `guest` (ID `20`) is configured on `en1`, dhcp client for the interface is disabled.
+* Primary VLAN `guest` (ID `20`) is configured on `en2`.
+* Interface `en3` is unchanged, primary VLAN `guest` (ID `20`) is unchanged.
+
+Running the function `$BridgePortVlan` with parameter `extra` applies another
+configuration:
+
+* Interface `en1` is unchanged.
+* Primary VLAN `extra` (via its ID `30`) is configured on `en2`.
+* Primary VLAN `extra` (ID `30`) is configured on `en3`.
+
+See also
+--------
+
+* [Wait for global functions und modules](../global-wait.md)
+* [Manage ports in bridge](bridge-port-to.md)
+
+---
+[⬅️ Go back to main README](../../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/mod/inspectvar.d/inspectvar.avif b/doc/mod/inspectvar.d/inspectvar.avif
new file mode 100644
index 0000000..f1da1d4
--- /dev/null
+++ b/doc/mod/inspectvar.d/inspectvar.avif
Binary files differ
diff --git a/doc/mod/inspectvar.md b/doc/mod/inspectvar.md
new file mode 100644
index 0000000..7ec74f2
--- /dev/null
+++ b/doc/mod/inspectvar.md
@@ -0,0 +1,40 @@
+Inspect variables
+=================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+RouterOS handles not just scalar variables, but also arrays - even nested.
+This module adds a function to inspect variables.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+ $ScriptInstallUpdate mod/inspectvar;
+
+Usage and invocation
+--------------------
+
+Call the function `$InspectVar` with a variable as parameter:
+
+ $InspectVar $ModeButton;
+
+![InspectVar](inspectvar.d/inspectvar.avif)
+
+---
+[⬅️ Go back to main README](../../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/mod/ipcalc.d/ipcalc.avif b/doc/mod/ipcalc.d/ipcalc.avif
new file mode 100644
index 0000000..fe726e8
--- /dev/null
+++ b/doc/mod/ipcalc.d/ipcalc.avif
Binary files differ
diff --git a/doc/mod/ipcalc.d/ipcalcreturn.avif b/doc/mod/ipcalc.d/ipcalcreturn.avif
new file mode 100644
index 0000000..5e4dd57
--- /dev/null
+++ b/doc/mod/ipcalc.d/ipcalcreturn.avif
Binary files differ
diff --git a/doc/mod/ipcalc.md b/doc/mod/ipcalc.md
new file mode 100644
index 0000000..5b24952
--- /dev/null
+++ b/doc/mod/ipcalc.md
@@ -0,0 +1,60 @@
+IP address calculation
+======================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module adds functions for IP address calculation.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+ $ScriptInstallUpdate mod/ipcalc;
+
+Usage and invocation
+--------------------
+
+### IPCalc
+
+The function `$IPCalc` prints information to terminal, including:
+
+* address
+* netmask
+* network in CIDR notation
+* minimum host address
+* maximum host address
+* broadcast address
+
+It expects an IP address in CIDR notation as argument.
+
+ $IPCalc 192.168.88.1/24;
+
+![IPCalc](ipcalc.d/ipcalc.avif)
+
+### IPCalcReturn
+
+The function `$IPCalcReturn` expects an IP address in CIDR notation as
+argument as well. But it does not print to terminal, instead it returns
+the information in a named array.
+
+ :put ([ $IPCalcReturn 192.168.88.1/24 ]->"broadcast");
+
+![IPCalcReturn](ipcalc.d/ipcalcreturn.avif)
+
+---
+[⬅️ Go back to main README](../../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/mod/notification-email.md b/doc/mod/notification-email.md
new file mode 100644
index 0000000..76816c1
--- /dev/null
+++ b/doc/mod/notification-email.md
@@ -0,0 +1,88 @@
+Send notifications via e-mail
+=============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module adds support for sending notifications via e-mail. A queue is
+used to make sure notifications are not lost on failure but sent later.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+ $ScriptInstallUpdate mod/notification-email;
+
+Also you need a valid e-mail account with smtp login credentials.
+
+Configuration
+-------------
+
+Set up your device's
+[e-mail settings](https://wiki.mikrotik.com/wiki/Manual:Tools/email).
+Also make sure the device has correct time configured, best is to set up
+the ntp client.
+
+Then edit `global-config-overlay`, add `EmailGeneralTo` with a valid
+recipient address. Finally reload the configuration.
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+### Sending to several recipients
+
+Sending notifications to several recipients is possible as well. Add
+`EmailGeneralCc` on top, which can have a single mail address or a comma
+separated list.
+
+Usage and invocation
+--------------------
+
+There's nothing special to do. Every script or function sending a notification
+will now send it to your e-mail account.
+
+But of course you can use the function to send notifications directly. Give
+it a try:
+
+ $SendEMail "Subject..." "Body...";
+
+Alternatively this sends a notification with all available and configured
+methods:
+
+ $SendNotification "Subject..." "Body...";
+
+To use the functions in your own scripts you have to declare them first.
+Place this before you call them:
+
+ :global SendEMail;
+ :global SendNotification;
+
+In case there is a situation when the queue needs to be purged there is a
+function available:
+
+ $PurgeEMailQueue;
+
+See also
+--------
+
+* [Send notifications via Matrix](notification-matrix.md)
+* [Send notifications via Ntfy](notification-ntfy.md)
+* [Send notifications via Telegram](notification-telegram.md)
+
+---
+[⬅️ Go back to main README](../../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/mod/notification-matrix.d/01-authenticate.avif b/doc/mod/notification-matrix.d/01-authenticate.avif
new file mode 100644
index 0000000..1db516b
--- /dev/null
+++ b/doc/mod/notification-matrix.d/01-authenticate.avif
Binary files differ
diff --git a/doc/mod/notification-matrix.d/02-join-room.avif b/doc/mod/notification-matrix.d/02-join-room.avif
new file mode 100644
index 0000000..edd6c81
--- /dev/null
+++ b/doc/mod/notification-matrix.d/02-join-room.avif
Binary files differ
diff --git a/doc/mod/notification-matrix.md b/doc/mod/notification-matrix.md
new file mode 100644
index 0000000..c68b0aa
--- /dev/null
+++ b/doc/mod/notification-matrix.md
@@ -0,0 +1,129 @@
+Send notifications via Matrix
+=============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module adds support for sending notifications via
+[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
+-----------------------------
+
+Just install the module:
+
+ $ScriptInstallUpdate mod/notification-matrix;
+
+Also install a Matrix client on at least one of your mobile and/or desktop
+devices. Create and setup an account there, we will reference that as
+"*general account*" later.
+
+Configuration
+-------------
+
+Edit `global-config-overlay`, add `MatrixHomeServer`, `MatrixAccessToken` and
+`MatrixRoom` - see below on hints how to retrieve this information. Then
+reload the configuration.
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+The Matrix server is connected via encrypted https, and certificate
+verification is applied. So make sure you have the certificate chain for
+your server in device's certificate store.
+
+> ℹ️ **Info**: The *matrix.org* server uses a Cloudflare certificate. You can
+> install that with: `$CertificateAvailable "Cloudflare Inc ECC CA-3"`
+
+### From other device
+
+If you have setup your Matrix *notification account* before just reuse that.
+Copy the relevant configuration to the device to be configured.
+
+### Setup new account
+
+As there is no privilege separation you should create a dedicated account
+for use with these scripts, in addition to your *general account*.
+We will reference that as "*notification account*" in the following steps.
+
+#### Authenticate
+
+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:
+
+ $SetupMatrixAuthenticate "@example:matrix.org" "v3ry-s3cr3t";
+
+![authenticate](notification-matrix.d/01-authenticate.avif)
+
+#### Join 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).
+
+Finally make the *notification account* join into the room by accepting
+the invite.
+
+ $SetupMatrixJoinRoom "!WUcxpSjKyxSGelouhA:matrix.org";
+
+![join room](notification-matrix.d/02-join-room.avif)
+
+The settings have been appended to `global-config-overlay`. You may want to
+edit to move it to an appropriate place.
+
+Usage and invocation
+--------------------
+
+There's nothing special to do. Every script or function sending a notification
+will now send it to your Matrix account.
+
+But of course you can use the function to send notifications directly. Give
+it a try:
+
+ $SendMatrix "Subject..." "Body...";
+
+Alternatively this sends a notification with all available and configured
+methods:
+
+ $SendNotification "Subject..." "Body...";
+
+To use the functions in your own scripts you have to declare them first.
+Place this before you call them:
+
+ :global SendMatrix;
+ :global SendNotification;
+
+In case there is a situation when the queue needs to be purged there is a
+function available:
+
+ $PurgeMatrixQueue;
+
+See also
+--------
+
+* [Send notifications via e-mail](notification-email.md)
+* [Send notifications via Ntfy](notification-ntfy.md)
+* [Send notifications via Telegram](notification-telegram.md)
+
+---
+[⬅️ Go back to main README](../../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/mod/notification-ntfy.md b/doc/mod/notification-ntfy.md
new file mode 100644
index 0000000..a3fdf88
--- /dev/null
+++ b/doc/mod/notification-ntfy.md
@@ -0,0 +1,86 @@
+Send notifications via Ntfy
+===========================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module adds support for sending notifications via
+[Ntfy](https://ntfy.sh/). A queue is used to make sure
+notifications are not lost on failure but sent later.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+ $ScriptInstallUpdate mod/notification-ntfy;
+
+Also install the Ntfy app on your mobile device or use the
+[web app](https://ntfy.sh/app) in a browser of your choice.
+
+Configuration
+-------------
+
+Creating an account is not required. Just choose a topic and you are good
+to go.
+
+> ⚠️ **Warning**: If you use ntfy without sign-up, the topic is essentially
+> a password, so pick something that's not easily guessable.
+
+Edit `global-config-overlay`, add `NtfyServer` (leave it unchanged, unless
+you are self-hosting the service) and `NtfyTopic` with your choosen topic.
+Then reload the configuration.
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+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
+--------
+
+* [Send notifications via e-mail](notification-email.md)
+* [Send notifications via Matrix](notification-matrix.md)
+* [Send notifications via Telegram](notification-telegram.md)
+
+---
+[⬅️ Go back to main README](../../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/mod/notification-telegram.d/newbot.avif b/doc/mod/notification-telegram.d/newbot.avif
new file mode 100644
index 0000000..1fc7355
--- /dev/null
+++ b/doc/mod/notification-telegram.d/newbot.avif
Binary files differ
diff --git a/doc/mod/notification-telegram.d/setuserpic.avif b/doc/mod/notification-telegram.d/setuserpic.avif
new file mode 100644
index 0000000..2017d20
--- /dev/null
+++ b/doc/mod/notification-telegram.d/setuserpic.avif
Binary files differ
diff --git a/doc/mod/notification-telegram.md b/doc/mod/notification-telegram.md
new file mode 100644
index 0000000..cb326f0
--- /dev/null
+++ b/doc/mod/notification-telegram.md
@@ -0,0 +1,112 @@
+Send notifications via Telegram
+===============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module adds support for sending notifications via
+[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
+-----------------------------
+
+Just install the module:
+
+ $ScriptInstallUpdate mod/notification-telegram;
+
+Also install Telegram on at least one of your mobile and/or desktop devices
+and create an account.
+
+Configuration
+-------------
+
+Open Telegram, then start a chat with [BotFather](https://t.me/BotFather) and
+create your own bot:
+
+![create new bot](notification-telegram.d/newbot.avif)
+
+Now open a chat with your bot and start it by clicking the `START` button.
+
+Open just another chat with [GetIDs Bot](https://t.me/getidsbot), again start
+with the `START` button. It will send you some information, including the
+`id`, just below `You`.
+
+Finally edit `global-config-overlay`, add `TelegramTokenId` with the token
+from *BotFather* and `TelegramChatId` with your id from *GetIDs Bot*. Then
+reload the configuration.
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+### Notifications to a group
+
+Sending notifications to a group is possible as well. Add your bot and the
+*GetIDs Bot* to a group, then use the group's id (which starts with a dash)
+for `TelegramChatId`. Then remove *GetIDs Bot* from group.
+
+Usage and invocation
+--------------------
+
+There's nothing special to do. Every script or function sending a notification
+will now send it to your Telegram account.
+
+But of course you can use the function to send notifications directly. Give
+it a try:
+
+ $SendTelegram "Subject..." "Body...";
+
+Alternatively this sends a notification with all available and configured
+methods:
+
+ $SendNotification "Subject..." "Body...";
+
+To use the functions in your own scripts you have to declare them first.
+Place this before you call them:
+
+ :global SendTelegram;
+ :global SendNotification;
+
+In case there is a situation when the queue needs to be purged there is a
+function available:
+
+ $PurgeTelegramQueue;
+
+Tips & Tricks
+-------------
+
+### Set a profile photo
+
+You can use a profile photo for your bot to make it recognizable. Open the
+chat with [BotFather](https://t.me/BotFather) and set it there.
+
+![set profile photo](notification-telegram.d/setuserpic.avif)
+
+Have a look at my
+[RouterOS-Scripts Logo Color Changer](https://git.eworm.de/cgit/routeros-scripts/plain/contrib/logo-color.html)
+to create a colored version of this scripts' logo.
+
+See also
+--------
+
+* [Chat with your router and send commands via Telegram bot](../telegram-chat.md)
+* [Send notifications via e-mail](notification-email.md)
+* [Send notifications via Matrix](notification-matrix.md)
+* [Send notifications via Ntfy](notification-ntfy.md)
+
+---
+[⬅️ Go back to main README](../../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/mod/scriptrunonce.d/hello-world.rsc b/doc/mod/scriptrunonce.d/hello-world.rsc
new file mode 100644
index 0000000..6404781
--- /dev/null
+++ b/doc/mod/scriptrunonce.d/hello-world.rsc
@@ -0,0 +1,3 @@
+#!rsc by RouterOS
+
+:put ("Hello World from " . [ /system/identity/get name ] . "!");
diff --git a/doc/mod/scriptrunonce.d/scriptrunonce.avif b/doc/mod/scriptrunonce.d/scriptrunonce.avif
new file mode 100644
index 0000000..27ccd41
--- /dev/null
+++ b/doc/mod/scriptrunonce.d/scriptrunonce.avif
Binary files differ
diff --git a/doc/mod/scriptrunonce.md b/doc/mod/scriptrunonce.md
new file mode 100644
index 0000000..6619efb
--- /dev/null
+++ b/doc/mod/scriptrunonce.md
@@ -0,0 +1,59 @@
+Download script and run it once
+===============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module adds a function that downloads a script, checks for syntax
+validity and runs it once.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+ $ScriptInstallUpdate mod/scriptrunonce;
+
+Configuration
+-------------
+
+The optional configuration goes to `global-config-overlay`.
+
+* `ScriptRunOnceBaseUrl`: base url, prepended to parameter
+* `ScriptRunOnceUrlSuffix`: url suffix, appended to parameter
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+If the parameter passed to the function is not a complete URL (starting
+with protocol `ftp://`, `http://`, `https://` or `sftp://`) the base-url is
+prepended, and file extension `.rsc` and url-suffix are appended.
+
+Usage and invocation
+--------------------
+
+The function `$ScriptRunOnce` expects an URL (or name if
+`ScriptRunOnceBaseUrl` is given) pointing to a script as parameter.
+
+ $ScriptRunOnce https://git.eworm.de/cgit/routeros-scripts/plain/doc/mod/scriptrunonce.d/hello-world.rsc;
+
+![ScriptRunOnce](scriptrunonce.d/scriptrunonce.avif)
+
+Giving multiple scripts is possible, separated by comma.
+
+---
+[⬅️ Go back to main README](../../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/mod/ssh-keys-import.md b/doc/mod/ssh-keys-import.md
new file mode 100644
index 0000000..3d81566
--- /dev/null
+++ b/doc/mod/ssh-keys-import.md
@@ -0,0 +1,73 @@
+Import ssh keys for public key authentication
+=============================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+RouterOS supports ssh login with public key authentication. The functions
+in this module help importing the keys.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+ $ScriptInstallUpdate mod/ssh-keys-import;
+
+Usage and invocation
+--------------------
+
+### Import single key from terminal
+
+Call the function `$SSHKeysImport` with key and user as parameter to
+import that key:
+
+ $SSHKeysImport "ssh-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;
+
+The third part of the key (`user` in this example) is inherited as
+`key-owner` in RouterOS. Also the `MD5` fingerprint is recorded, this helps
+to audit and verify the available keys.
+
+> ℹ️️ **Info**: Use `ssh-keygen` to show a fingerprint of an existing public
+> key file: `ssh-keygen -l -E md5 -f ~/.ssh/id_ed25519.pub`
+
+### Import several keys from file
+
+The functions `$SSHKeysImportFile` can read an `authorized_keys`-style file
+and import all the keys. The user given to the function can be overwritting
+from comments in the file. Create a file `keys.pub` with this content:
+
+```
+ssh-ed25519 AAAAC3Nza...3OcN8A user@client
+ssh-rsa AAAAB3Nza...ozyts= worker@station
+# user=example
+ssh-rsa AAAAB3Nza...GXQVk= person@host
+```
+
+Then import it with:
+
+ $SSHKeysImportFile keys.pub admin;
+
+This will import the first two keys for user `admin` (as given to function)
+and the third one for user `example` (as defined in comment).
+
+---
+[⬅️ Go back to main README](../../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/mode-button.md b/doc/mode-button.md
index 8d037e5..22ec215 100644
--- a/doc/mode-button.md
+++ b/doc/mode-button.md
@@ -1,7 +1,17 @@
Mode button with multiple presses
=================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -10,17 +20,17 @@ This script extend the functionality of mode button. Instead of just one
you can trigger several actions by pressing the mode button several times.
The hardware needs to have a mode button, see
-`/ system routerboard mode-button`. Starting with RouterOS 6.47beta60 you
+`/system/routerboard/mode-button`. Starting with RouterOS 6.47beta60 you
can configure the reset button to act the same, see
-`/ system routerboard reset-button`.
+`/system/routerboard/reset-button`.
Copy this code to terminal to check:
```
-:if ([ :len [ /system routerboard mode-button print as-value ] ] > 0) do={
+:if ([ :len [ /system/routerboard/mode-button/print as-value ] ] > 0) do={
:put "Mode button is supported.";
} else={
- :if ([ :len [ /system routerboard reset-button print as-value ] ] > 0) do={
+ :if ([ :len [ /system/routerboard/reset-button/print as-value ] ] > 0) do={
:put "Mode button is not supported, but reset button is.";
} else={
:put "Neither mode button nor reset button is supported.";
@@ -37,11 +47,11 @@ Just install the script:
Then configure the mode button to run `mode-button`:
- / system routerboard mode-button set enabled=yes on-event="/ system script run mode-button;";
+ /system/routerboard/mode-button/set enabled=yes on-event="/system/script/run mode-button;";
To use the reset button instead:
- / system routerboard reset-button set enabled=yes on-event="/ system script run mode-button;";
+ /system/routerboard/reset-button/set enabled=yes on-event="/system/script/run mode-button;";
Configuration
-------------
@@ -49,13 +59,17 @@ 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
+> your local `global-config-overlay` and modify it to your specific needs.
Usage and invocation
--------------------
-Press the mode button. :)
+Press the mode button. 😜
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/netwatch-dns.md b/doc/netwatch-dns.md
new file mode 100644
index 0000000..e00ccd0
--- /dev/null
+++ b/doc/netwatch-dns.md
@@ -0,0 +1,94 @@
+Manage DNS and DoH servers from netwatch
+========================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script reads server state from netwatch and manages used DNS and
+DoH (DNS over HTTPS) servers.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+ $ScriptInstallUpdate netwatch-dns;
+
+Then add a scheduler to run it periodically:
+
+ /system/scheduler/add interval=1m name=netwatch-dns on-event="/system/script/run netwatch-dns;" start-time=startup;
+
+Configuration
+-------------
+
+The DNS and DoH servers to be checked have to be added to netwatch with
+specific comment:
+
+ /tool/netwatch/add comment="doh" host=1.1.1.1;
+ /tool/netwatch/add comment="dns" host=8.8.8.8;
+ /tool/netwatch/add comment="doh, dns" host=9.9.9.9;
+
+This will configure *cloudflare-dns* for DoH (`https://1.1.1.1/dnsquery`), and
+*google-dns* and *quad-nine* for regular DNS (`8.8.8.8,9.9.9.9`) if up.
+If *cloudflare-dns* is down the script will fall back to *quad-nine* for DoH.
+
+Giving a specific query url for DoH is possible:
+
+ /tool/netwatch/add comment="doh, doh-url=https://dns.nextdns.io/dns-query" host=199.247.16.158;
+
+Note that using a name in DoH url may introduce a chicken-and-egg issue!
+
+Adding a static DNS record has the same result for the url, but always
+resolves to the same address.
+
+ /ip/dns/static/add name="dns.nextdns.io" address=199.247.16.158;
+ /tool/netwatch/add comment="doh" host=199.247.16.158;
+
+Be aware that you have to keep the ip address in sync with real world
+manually!
+
+Importing a certificate automatically is possible, at least if available in
+the repository (see `certs` sub directory).
+
+ /tool/netwatch/add comment="doh, doh-cert=DigiCert Global G2 TLS RSA SHA256 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;
+
+Sometimes using just one specific (possibly internal) DNS server may be
+desired, with fallback in case it fails. This is possible as well:
+
+ /tool/netwatch/add comment="dns" host=10.0.0.10;
+ /tool/netwatch/add comment="dns-fallback" host=1.1.1.1;
+
+Tips & Tricks
+-------------
+
+### Use in combination with notifications
+
+Netwatch entries can be created to work with both - this script and
+[netwatch-notify](netwatch-notify.md). Just give options for both:
+
+ /tool/netwatch/add comment="doh, notify, name=cloudflare-dns" host=1.1.1.1;
+
+Also this allows to update host address, see option `resolve`.
+
+See also
+--------
+
+* [Notify on host up and down](netwatch-notify.md)
+
+---
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/netwatch-notify.d/notification-01-down.avif b/doc/netwatch-notify.d/notification-01-down.avif
new file mode 100644
index 0000000..894fb23
--- /dev/null
+++ b/doc/netwatch-notify.d/notification-01-down.avif
Binary files differ
diff --git a/doc/netwatch-notify.d/notification-02-up.avif b/doc/netwatch-notify.d/notification-02-up.avif
new file mode 100644
index 0000000..9021a93
--- /dev/null
+++ b/doc/netwatch-notify.d/notification-02-up.avif
Binary files differ
diff --git a/doc/netwatch-notify.md b/doc/netwatch-notify.md
index 11371ff..806bb3a 100644
--- a/doc/netwatch-notify.md
+++ b/doc/netwatch-notify.md
@@ -1,17 +1,32 @@
Notify on host up and down
==========================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
This script sends notifications about host UP and DOWN events. In comparison
-to just netwatch (`/ tool netwatch`) and its `up-script` and `down-script`
+to just netwatch (`/tool/netwatch`) and its `up-script` and `down-script`
this script implements a simple state machine and dependency model. Host
down events are triggered only if the host is down for several checks and
optional parent host is not down to avoid false alerts.
+### Sample notifications
+
+![netwatch-notify notification down](netwatch-notify.d/notification-01-down.avif)
+![netwatch-notify notification up](netwatch-notify.d/notification-02-up.avif)
+
Requirements and installation
-----------------------------
@@ -21,36 +36,145 @@ Just install the script:
Then add a scheduler to run it periodically:
- / system scheduler add interval=1m name=netwatch-notify on-event="/ system script run netwatch-notify;" start-time=startup;
+ /system/scheduler/add interval=1m name=netwatch-notify on-event="/system/script/run netwatch-notify;" start-time=startup;
Configuration
-------------
The hosts to be checked have to be added to netwatch with specific comment:
- / tool netwatch add comment="notify, hostname=example.com" host=[ :resolve "example.com" ];
+ /tool/netwatch/add comment="notify, name=example.com" host=[ :resolve "example.com" ];
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+### Hooks
It is possible to run an up hook command (`up-hook`) or down hook command
(`down-hook`) when a notification is triggered. This has to be added in
-comment:
+comment, note that some characters need extra escaping:
- / tool netwatch add comment="notify, hostname=poe-device, down-hook=/ interface ethernet poe power-cycle en21;" host=10.0.0.20;
+ /tool/netwatch/add comment=("notify, name=device, down-hook=/interface/ethernet \\{ disable \\\"en2\\\"; enable \\\"en2\\\"; \\}") host=10.0.0.20;
+
+Also there is a `pre-down-hook` that fires at two thirds of failed checks
+required for the notification. The idea is to fix the issue before a
+notification is sent.
+
+Getting the escaping right may be troublesome. Please consider adding a
+script in `/system/script`, then running that from hook.
+
+### Count threshould
The count threshould (default is 5 checks) is configurable as well:
- / tool netwatch add comment="notify, hostname=example.com, count=10" host=104.18.144.11;
+ /tool/netwatch/add comment="notify, name=example.com, count=10" host=104.18.144.11;
+
+### Parents & dependencies
If the host is behind another checked host add a dependency, this will
suppress notification if the parent host is down:
- / tool netwatch add comment="notify, hostname=gateway" host=93.184.216.1;
- / tool netwatch add comment="notify, hostname=example.com, parent=gateway" host=93.184.216.34;
+ /tool/netwatch/add comment="notify, name=gateway" host=93.184.216.1;
+ /tool/netwatch/add comment="notify, name=example.com, parent=gateway" host=93.184.216.34;
Note that every configured parent in a chain increases the check count
threshould by one.
-Also notification settings are required for e-mail and telegram.
+### 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";
+
+This supports multiple A or AAAA 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.
+
+### No notification on host down
+
+Also suppressing the notification on host down is possible with parameter
+`no-down-notification`. This may be desired for devices that are usually
+powered off, but accessibility is of interest.
+
+ /tool/netwatch/add comment="notify, name=printer, no-down-notification" host=10.0.0.30;
+
+Go and get your coffee ☕️ before sending the print job.
+
+### Add a note in notification
+
+For some extra information it is possible to add a text note. This is
+included verbatim into the notification.
+
+ /tool/netwatch/add comment="notify, name=example, note=Do not touch!" host=10.0.0.31;
+
+### Add a link in notification
+
+It is possible to add a link in notification, that is added below the
+formatted notification text.
+
+ /tool/netwatch/add comment="notify, name=example.com, resolve=example.com, link=https://example.com/";
+
+Tips & Tricks
+-------------
+
+### One of several hosts
+
+Sometimes it is sufficient if one of a number of hosts is available. You can
+make `netwatch-notify` check for that by adding several items with same
+`name`. Note that `count` has to be multiplied to keep the actual time.
+
+ /tool/netwatch/add comment="notify, name=service, count=10" host=10.0.0.10;
+ /tool/netwatch/add comment="notify, name=service, count=10" host=10.0.0.20;
+
+### Checking internet connectivity
+
+Sometimes you can not check your gateway for internet connectivity, for
+example when it does not respond to pings or has a dynamic address. You could
+check `1.1.1.1` (Cloudflare DNS), `9.9.9.9` (Quad-nine DNS), `8.8.8.8`
+(Google DNS) or any other reliable address that indicates internet
+connectivity.
+
+ /tool/netwatch/add comment="notify, name=internet" host=1.1.1.1;
+
+A target like this suits well to be parent for other checks.
+
+ /tool/netwatch/add comment="notify, name=example.com, parent=internet" host=93.184.216.34;
+
+### Checking specific ISP
+
+Having several ISPs for redundancy a failed link may go unnoticed without
+proper monitoring. You can use routing-mark to monitor specific connections.
+Create a route and firewall mangle rule.
+
+ /routing/table/add fib name=via-isp1;
+ /ip/route/add distance=1 gateway=isp1 routing-table=via-isp1;
+ /ip/firewall/mangle/add action=mark-routing chain=output new-routing-mark=via-isp1 dst-address=1.0.0.1 passthrough=yes;
+
+Finally monitor the address with `netwatch-notify`.
+
+ /tool/netwatch/add comment="notify, name=quad-one via isp1" host=1.0.0.1;
+
+Note that *all* traffic to the given address is routed that way. In case of
+link failure this address is not available, so use something reliable but
+non-essential. In this example the address `1.0.0.1` is used, the same service
+(Cloudflare DNS) is available at `1.1.1.1`.
+
+### Use in combination with DNS and DoH management
+
+Netwatch entries can be created to work with both - this script and
+[netwatch-dns](netwatch-dns.md). Just give options for both:
+
+ /tool/netwatch/add comment="doh, notify, name=cloudflare-dns" host=1.1.1.1;
+
+See also
+--------
+
+* [Manage DNS and DoH servers from netwatch](netwatch-dns.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/netwatch-syslog.md b/doc/netwatch-syslog.md
index 9a28bb9..6a337d4 100644
--- a/doc/netwatch-syslog.md
+++ b/doc/netwatch-syslog.md
@@ -1,34 +1,5 @@
-Manage remote logging
-=====================
+This script has been dropped. Filtering in firewall is advised, which should
+look something like this:
-[◀ Go back to main README](../README.md)
-
-Description
------------
-
-RouterOS supports sending log messages via network to a remote syslog server.
-If the server is not available no log messages (with potentially sensitive
-information) should be sent. This script disables remote logging by
-availability.
-
-Requirements and installation
------------------------------
-
-Let's assume there is a remote log action and associated logging rule:
-
- / system logging action set remote=10.0.0.1 [ find where name="remote" ];
- / system logging add action=remote topics=info;
-
-Just install the script:
-
- $ScriptInstallUpdate netwatch-syslog;
-
-... and create a netwatch matching the IP address from logging action above:
-
- / tool netwatch add down-script=netwatch-syslog host=10.0.0.1 up-script=netwatch-syslog;
-
-All logging rules are disabled when host is down.
-
----
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+ /ip/firewall/filter/add action=reject chain=output out-interface-list=WAN port=514 protocol=udp reject-with=icmp-admin-prohibited;
+ /ip/firewall/filter/add action=reject chain=forward out-interface-list=WAN port=514 protocol=udp reject-with=icmp-admin-prohibited;
diff --git a/doc/ospf-to-leds.md b/doc/ospf-to-leds.md
index 72fab6b..a7d4e9a 100644
--- a/doc/ospf-to-leds.md
+++ b/doc/ospf-to-leds.md
@@ -1,7 +1,17 @@
Visualize OSPF state via LEDs
=============================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -19,7 +29,7 @@ Just install the script:
... and add a scheduler to run the script periodically:
- / system scheduler add interval=20s name=ospf-to-leds on-event="/ system script run ospf-to-leds;" start-time=startup;
+ /system/scheduler/add interval=20s name=ospf-to-leds on-event="/system/script/run ospf-to-leds;" start-time=startup;
Configuration
-------------
@@ -27,8 +37,8 @@ Configuration
The configuration goes to OSPF instance's comment. To visualize state for
instance `default` via LED `user-led` set this:
- / routing ospf instance set default comment="ospf-to-leds, leds=user-led";
+ /routing/ospf/instance/set default comment="ospf-to-leds, leds=user-led";
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/packages-update.md b/doc/packages-update.md
index 882ce80..fae3896 100644
--- a/doc/packages-update.md
+++ b/doc/packages-update.md
@@ -1,20 +1,34 @@
Manage system update
====================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.13-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
In rare cases RouterOS fails to properly downlaod package on update
-(`/ system package update install`), resulting in borked system with missing
+(`/system/package/update/install`), resulting in borked system with missing
packages. This script tries to avoid this situation by doing some basic
verification.
But it provides some extra functionality:
-* send backup via e-mail if [email-backup](email-backup.md) is installed
-* upload backup if [upload-backup](upload-backup.md) is installed
+* upload backup to Mikrotik cloud if [backup-cloud](backup-cloud.md) is
+ installed
+* send backup via e-mail if [backup-email](backup-email.md) is installed
+* save configuration to fallback partition if
+ [backup-partition](backup-partition.md) is installed
+* upload backup to server if [backup-upload](backup-upload.md) is installed
* schedule reboot at night
Requirements and installation
@@ -27,20 +41,38 @@ Just install the script:
It is automatically run by [check-routeros-update](check-routeros-update.md)
if available.
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, this is the only parameter:
+
+* `PackagesUpdateDeferReboot`: defer the reboot for night (between 3 AM
+ and 5 AM)
+
+By modifying the scheduler's `start-time` you can force the reboot at
+different time.
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
Usage and invocation
--------------------
Alternatively run it manually:
- / system script run packages-update;
+ /system/script/run packages-update;
See also
--------
+* [Upload backup to Mikrotik cloud](backup-cloud.md)
+* [Send backup via e-mail](backup-email.md)
+* [Save configuration to fallback partition](backup-partition.md)
+* [Upload backup to server](backup-upload.md)
* [Notify on RouterOS update](check-routeros-update.md)
-* [Send backup via e-mail](email-backup.md)
-* [Upload backup to server](upload-backup.md)
+* [Automatically upgrade firmware and reboot](firmware-upgrade-reboot.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/ppp-on-up.md b/doc/ppp-on-up.md
index 432a640..418f05e 100644
--- a/doc/ppp-on-up.md
+++ b/doc/ppp-on-up.md
@@ -1,7 +1,17 @@
Run scripts on ppp connection
=============================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -21,7 +31,7 @@ Just install the script:
... and make it the `on-up` script for ppp profile:
- / ppp profile set on-up=ppp-on-up [ find ];
+ /ppp/profile/set on-up=ppp-on-up [ find ];
See also
--------
@@ -30,5 +40,5 @@ See also
* [Update tunnelbroker configuration](update-tunnelbroker.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/rotate-ntp.md b/doc/rotate-ntp.md
index eb04f5c..9a016a3 100644
--- a/doc/rotate-ntp.md
+++ b/doc/rotate-ntp.md
@@ -1,40 +1,3 @@
-Rotate NTP servers
-==================
-
-[◀ Go back to main README](../README.md)
-
-Description
------------
-
-RouterOS requires NTP servers to be configured by IP address. Servers from a
-pool may appear and disappear, leaving broken NTP configuration.
-
-This script allows to rotate IP addresses from a given pool.
-
-Requirements and installation
------------------------------
-
-Just install the script:
-
- $ScriptInstallUpdate rotate-ntp;
-
-Configuration
--------------
-
-The configuration goes to `global-config-overlay`, this is the parameter:
-
-* `NtpPool`: dns name of ntp server pool
-
-Usage and invocation
---------------------
-
-Just run the script to update the NTP configuration with actual IP
-addresses from pool if required.
-
-Alternatively a scheduler can be created:
-
- / system scheduler add interval=5d name=rotate-ntp on-event="/ system script run rotate-ntp;" start-time=startup;
-
----
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+This script has been dropped as the limitation does no longer exist with
+RouterOS 7.x, where you can enable a ntp server and use a name for the client
+at the same time.
diff --git a/doc/sms-action.md b/doc/sms-action.md
index df9e14f..18ca574 100644
--- a/doc/sms-action.md
+++ b/doc/sms-action.md
@@ -1,7 +1,17 @@
Act on received SMS
===================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -25,9 +35,13 @@ The configuration goes to `global-config-overlay`, this is the only parameter:
* `SmsAction`: an array with pre-defined actions
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
Then enable SMS actions:
- / tool sms set allowed-number=+491234567890 receive-enabled=yes secret=s3cr3t;
+ /tool/sms/set allowed-number=+491234567890 receive-enabled=yes secret=s3cr3t;
Usage and invocation
--------------------
@@ -45,5 +59,5 @@ See also
* [Forward received SMS](sms-forward.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/sms-forward.d/notification.avif b/doc/sms-forward.d/notification.avif
new file mode 100644
index 0000000..01eb7ba
--- /dev/null
+++ b/doc/sms-forward.d/notification.avif
Binary files differ
diff --git a/doc/sms-forward.md b/doc/sms-forward.md
index 27c3847..2fe9486 100644
--- a/doc/sms-forward.md
+++ b/doc/sms-forward.md
@@ -1,7 +1,17 @@
Forward received SMS
====================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -10,6 +20,10 @@ RouterOS can receive SMS. This script forwards SMS as notification.
A broadband interface with SMS support is required.
+### Sample notification
+
+![sms-forward notification](sms-forward.d/notification.avif)
+
Requirements and installation
-----------------------------
@@ -19,15 +33,60 @@ Just install the script:
... and add a scheduler to run it periodically:
- / system scheduler add interval=2m name=sms-forward on-event="/ system script run sms-forward;" start-time=startup;
+ /system/scheduler/add interval=2m name=sms-forward on-event="/system/script/run sms-forward;" start-time=startup;
Configuration
-------------
-Notification settings are required for e-mail and telegram. Also you have
-to enable receiving of SMS:
+You have to enable receiving of SMS:
+
+ /tool/sms/set receive-enabled=yes;
+
+The configuration goes to `global-config-overlay`, this is the only parameter:
+
+* `SmsForwardHooks`: an array with pre-defined hooks, where each hook consists
+ of `match` (which is matched against the received message), `allowed-number`
+ (which is matched against the sending phone number or name) and `command`.
+ For `match` and `allowed-number` regular expressions are supported. Actual
+ phone number (`$Phone`) and message (`$Message`) are available for the hook.
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Notification settings are required for
+[e-mail](mod/notification-email.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+Tips & Tricks
+-------------
+
+### Take care of harmful commands!
+
+It is easy to fake the sending phone number! So make sure you do not rely on
+that number for potentially harmful commands. Add a shared secret to match
+into the text instead, for example: `reboot-53cr3t-5tr1n9` instead of just
+`reboot`.
+
+### Order new volume
+
+Most broadband providers include a volume limit for their data plans. The
+hook functionality can be used to order new volume automatically.
+
+Let's assume an imaginary provider **ABC** sends a message when the available
+volume is about to deplete. The message is sent from `ABC` and the text
+contains the string `80%`. New volume can be ordered by sending a SMS back to
+the phone number `1234` with the text `data-plan`.
+
+ :global SmsForwardHooks {
+ { match="80%";
+ allowed-number="ABC";
+ command="/tool/sms/send lte1 phone-number=1234 message=\"data-plan\";" };
+ };
- / tool sms set receive-enabled=yes;
+Adjust the values to your own needs.
See also
--------
@@ -35,5 +94,5 @@ See also
* [Act on received SMS](sms-action.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/ssh-keys-import.md b/doc/ssh-keys-import.md
index d221072..d1325aa 100644
--- a/doc/ssh-keys-import.md
+++ b/doc/ssh-keys-import.md
@@ -1,33 +1,2 @@
-Import SSH keys
-===============
-
-[◀ Go back to main README](../README.md)
-
-Description
------------
-
-This script imports public SSH keys (files with extension "`pub`") into
-local store for user authentication.
-
-Requirements and installation
------------------------------
-
-Just install the script:
-
- $ScriptInstallUpdate ssh-keys-import;
-
-Usage and invocation
---------------------
-
-Copy files with extension "`pub`" containing public SSH keys for your device.
-Then run the script:
-
- / system script run ssh-keys-import;
-
-Starting with an `authorized_keys` file you can split it on a shell:
-
- while read type key name; do echo $type $key $name > $name.pub; done < authorized_keys
-
----
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+This script has been replaced by a module. Please see
+[Import ssh keys for public key authentication](mod/ssh-keys-import.md).
diff --git a/doc/super-mario-theme.md b/doc/super-mario-theme.md
index 68484dc..ec59b39 100644
--- a/doc/super-mario-theme.md
+++ b/doc/super-mario-theme.md
@@ -1,7 +1,14 @@
Play Super Mario theme
======================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
Description
-----------
@@ -22,10 +29,10 @@ Usage and invocation
Just run the script to play:
- / system script run super-mario-theme;
+ /system/script/run super-mario-theme;
For extra fun use it for dhcp lease script. :)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/telegram-chat.d/01-chat-specific.avif b/doc/telegram-chat.d/01-chat-specific.avif
new file mode 100644
index 0000000..ab75f78
--- /dev/null
+++ b/doc/telegram-chat.d/01-chat-specific.avif
Binary files differ
diff --git a/doc/telegram-chat.d/02-chat-all.avif b/doc/telegram-chat.d/02-chat-all.avif
new file mode 100644
index 0000000..ed1a389
--- /dev/null
+++ b/doc/telegram-chat.d/02-chat-all.avif
Binary files differ
diff --git a/doc/telegram-chat.d/03-reply.avif b/doc/telegram-chat.d/03-reply.avif
new file mode 100644
index 0000000..515853e
--- /dev/null
+++ b/doc/telegram-chat.d/03-reply.avif
Binary files differ
diff --git a/doc/telegram-chat.md b/doc/telegram-chat.md
new file mode 100644
index 0000000..2a4af99
--- /dev/null
+++ b/doc/telegram-chat.md
@@ -0,0 +1,153 @@
+Chat with your router and send commands via Telegram bot
+========================================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script makes your device poll a Telegram bot for new messages. With
+these messages you can send commands to your device and make it run them.
+The resulting output is send back to you.
+
+Requirements and installation
+-----------------------------
+
+Just install the script and the module for notifications via Telegram:
+
+ $ScriptInstallUpdate telegram-chat,mod/notification-telegram;
+
+Then create a schedule that runs the script periodically:
+
+ /system/scheduler/add start-time=startup interval=30s name=telegram-chat on-event="/system/script/run telegram-chat;";
+
+> ⚠️ **Warning**: Make sure to keep the interval in sync when installing
+> on several devices. Differing polling intervals will result in missed
+> messages.
+
+Configuration
+-------------
+
+Make sure to configure
+[notifications via telegram](mod/notification-telegram.md) first. The
+additional configuration goes to `global-config-overlay`, these are the
+parameters:
+
+* `TelegramChatIdsTrusted`: an array with trusted chat ids or user names
+* `TelegramChatGroups`: define the groups a device should belong to
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Usage and invocation
+--------------------
+
+### Activating device(s)
+
+This script is capable of chatting with multiple devices. By default a
+device is passive and not acting on messages. To activate it send a message
+containing `! identity` (exclamation mark, optional space and system's
+identity). To query all dynamic ip addresses form a device named "*MikroTik*"
+send `! MikroTik`, followed by `/ip/address/print where dynamic;`.
+
+![chat to specific device](telegram-chat.d/01-chat-specific.avif)
+
+Devices can be grouped to chat with them simultaneously. The default group
+"*all*" can be activated by sending `! @all`, which will make all devices
+act on your commands.
+
+![chat to all devices](telegram-chat.d/02-chat-all.avif)
+
+Send a single exclamation mark or non-existent identity to make all
+devices passive again.
+
+### Reply to message
+
+Let's assume you received a message from a device before, and want to send
+a command to that device. No need to activate it, you can just reply to
+that message.
+
+![reply to message](telegram-chat.d/03-reply.avif)
+
+Associated messages are cleared on device reboot.
+
+### Ask for devices
+
+Send a message with a single question mark (`?`) to query for devices
+currenty online. The answer can be used for command via reply then.
+
+Known limitations
+-----------------
+
+### Do not use numeric ids!
+
+Numeric ids are valid within a session only. Usually you can use something
+like this to print all ip addresses and remove the first one:
+
+ /ip/address/print;
+ /ip/address/remove 0;
+
+This will fail when sent in separate messages. Instead you should use basic
+scripting capabilities. Try to print what you want to act on...
+
+ /ip/address/print where interface=eth;
+
+... verify and finally remove it.
+
+ /ip/address/remove [ find where interface=eth ];
+
+What does work is using the persistent ids:
+
+ /ip/address/print show-ids;
+
+The output contains an id starting with asterisk that can be used:
+
+ /ip/address/remove *E;
+
+### Mind command runtime
+
+The command is run in background while the script waits for it - about
+20 seconds at maximum. A command exceeding that time continues to run in
+background, but the output in the message is missing or truncated then.
+
+If you still want a response you can work around this by making your code
+send information on its own. Something like this should do the job:
+
+ :global SendTelegram;
+ :delay 30s;
+ $SendTelegram "Command finished" "Your command finished...";
+
+### Output size
+
+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.
+
+### Sending commands to a group
+
+Adding a bot to a group allows it to send messages to that group. To allow
+it to receive messages you have to make it an admin of that group! It is
+fine to deny all permissions, though.
+
+Also adding an admin to a group can cause the group id to change, so check
+that if notifications break suddenly.
+
+See also
+--------
+
+* [Send notifications via Telegram](mod/notification-telegram.md)
+
+---
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/unattended-lte-firmware-upgrade.md b/doc/unattended-lte-firmware-upgrade.md
index 65801a6..a6bf994 100644
--- a/doc/unattended-lte-firmware-upgrade.md
+++ b/doc/unattended-lte-firmware-upgrade.md
@@ -1,7 +1,14 @@
Install LTE firmware upgrade
============================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
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.
@@ -32,7 +40,7 @@ Usage and invocation
Run the script if an upgrade for your LTE hardware is available:
- / system script run unattended-lte-firmware-upgrade;
+ /system/script/run unattended-lte-firmware-upgrade;
Then be patient, go for a coffee and wait for the upgrade process to finish.
@@ -42,5 +50,5 @@ See also
* [Notify on LTE firmware upgrade](check-lte-firmware-upgrade.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/update-gre-address.md b/doc/update-gre-address.md
index 870759e..fba2a65 100644
--- a/doc/update-gre-address.md
+++ b/doc/update-gre-address.md
@@ -1,7 +1,17 @@
Update GRE configuration with dynamic addresses
===============================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -23,7 +33,7 @@ Just install the script:
... and add a scheduler to run the script periodically:
- / system scheduler add interval=30s name=update-gre-address on-event="/ system script run update-gre-address;" start-time=startup;
+ /system/scheduler/add interval=30s name=update-gre-address on-event="/system/script/run update-gre-address;" start-time=startup;
Configuration
-------------
@@ -31,8 +41,8 @@ Configuration
The configuration goes to interface's comment. Add the client's IKEv2
certificate CN into the comment:
- / interface gre set comment="ikev2-client1" gre-client1;
+ /interface/gre/set comment="ikev2-client1" gre-client1;
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/update-tunnelbroker.md b/doc/update-tunnelbroker.md
index 3641588..5aca581 100644
--- a/doc/update-tunnelbroker.md
+++ b/doc/update-tunnelbroker.md
@@ -1,7 +1,17 @@
Update tunnelbroker configuration
=================================
-[◀ Go back to main README](../README.md)
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.12-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
Description
-----------
@@ -25,11 +35,10 @@ Configuration
The configuration goes to interface's comment:
- / interface 6to4 set comment="tunnelbroker, user=user, pass=s3cr3t, id=12345" tunnelbroker;
-
-Also enabling dynamic DNS in Mikrotik cloud is required:
+ /interface/6to4/set comment="tunnelbroker, user=user, id=12345, pass=s3cr3t" tunnelbroker;
- / ip cloud set ddns-enabled=yes;
+You should know you user name from login. The `id` is the tunnel's numeric
+id, `pass` is the *update key* found on the tunnel's advanced tab.
See also
--------
@@ -37,5 +46,5 @@ See also
* [Run scripts on ppp connection](ppp-on-up.md)
---
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+[⬅️ Go back to main README](../README.md)
+[⬆️ Go back to top](#top)
diff --git a/doc/upload-backup.md b/doc/upload-backup.md
index e6ba9e2..83c9991 100644
--- a/doc/upload-backup.md
+++ b/doc/upload-backup.md
@@ -1,63 +1 @@
-Upload backup to server
-=======================
-
-[◀ Go back to main README](../README.md)
-
-Description
------------
-
-This script uploads binary backup (`/ system backup save`) and complete
-configuration export (`/ export terse`) to external server.
-
-Requirements and installation
------------------------------
-
-Just install the script:
-
- $ScriptInstallUpdate upload-backup;
-
-Configuration
--------------
-
-The configuration goes to `global-config-overlay`, these are the parameters:
-
-* `BackupSendBinary`: whether to send binary backup
-* `BackupSendExport`: whether to send configuration export
-* `BackupPassword`: password to encrypt the backup with
-* `BackupRandomDelay`: delay up to amount of seconds when run from scheduler
-* `BackupUploadUrl`: url to upload to
-* `BackupUploadUser`: username for server authentication
-* `BackupUploadPass`: password for server authentication
-
-Also notification settings are required for e-mail and telegram.
-
-### Issues with SFTP client
-
-The RouterOS SFTP client is picky if it comes to authentication methods.
-I had to disable all but password authentication on server side. For openssh
-edit `/etc/ssh/sshd_config` and add a directive like this, changed for your
-needs:
-
- Match User mikrotik
- AuthenticationMethods password
-
-Usage and invocation
---------------------
-
-Just run the script:
-
- / system script run upload-backup;
-
-Creating a scheduler may be an option:
-
- / system scheduler add interval=1w name=upload-backup on-event="/ system script run upload-backup;" start-time=09:25:00;
-
-See also
---------
-
-* [Send backup via e-mail](email-backup.md)
-* [Upload backup to Mikrotik cloud](cloud-backup.md)
-
----
-[◀ Go back to main README](../README.md)
-[▲ Go back to top](#top)
+This script has been renamed. Please see [backup-upload](backup-upload.md).
diff --git a/early-errors b/early-errors
deleted file mode 100644
index 7c2e509..0000000
--- a/early-errors
+++ /dev/null
@@ -1,6 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: early-errors
-
-:global LogPrintExit2;
-
-$LogPrintExit2 warning "early-errors" ("This script has been replaced. Please migrate to 'log-forward'.") true;
diff --git a/email-backup b/email-backup
deleted file mode 100644
index b9e91c2..0000000
--- a/email-backup
+++ /dev/null
@@ -1,75 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: email-backup
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# create and email backup and config file
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/email-backup.md
-
-:local 0 "email-backup";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global BackupPassword;
-:global BackupRandomDelay;
-:global BackupSendBinary;
-:global BackupSendExport;
-:global Domain;
-:global EmailBackupCc;
-:global EmailBackupTo;
-:global Identity;
-
-:global CharacterReplace;
-:global DeviceInfo;
-:global LogPrintExit2;
-:global RandomDelay;
-:global ScriptFromTerminal;
-:global WaitForFile;
-:global WaitFullyConnected;
-
-:if ($BackupSendBinary != true && \
- $BackupSendExport != true) do={
- $LogPrintExit2 error $0 ("Configured to send neither backup nor config export.") true;
-}
-
-:if ([ :len $EmailBackupTo ] = 0) do={
- $LogPrintExit2 error $0 ("Configuration is missing recipient for e-mail backup.") true;
-}
-
-$WaitFullyConnected;
-
-:if ([ $ScriptFromTerminal $0 ] = false && $BackupRandomDelay > 0) do={
- $RandomDelay $BackupRandomDelay;
-}
-
-# filename based on identity
-:local FileName [ $CharacterReplace ($Identity . "." . $Domain) "." "_" ];
-:local BackupFile "none";
-:local ConfigFile "none";
-:local Attach [ :toarray "" ];
-
-# binary backup
-:if ($BackupSendBinary = true) do={
- / system backup save encryption=aes-sha256 name=$FileName password=$BackupPassword;
- $WaitForFile ($FileName . ".backup");
- :set BackupFile ($FileName . ".backup");
- :set Attach ($Attach, $BackupFile);
-}
-
-# create configuration export
-:if ($BackupSendExport = true) do={
- / export terse file=$FileName;
- $WaitForFile ($FileName . ".rsc");
- :set ConfigFile ($FileName . ".rsc");
- :set Attach ($Attach, $ConfigFile);
-}
-
-# send email with status and files
-/ tool e-mail send to=$EmailBackupTo cc=$EmailBackupCc \
- subject=("[" . $Identity . "] Backup & Config") \
- body=("See attached files for backup and config export for " . \
- $Identity . ".\n\n" . \
- [ $DeviceInfo ] . "\n\n" . \
- "Backup file: " . $BackupFile . "\n" . \
- "Config file: " . $ConfigFile) \
- file=$Attach;
diff --git a/firmware-upgrade-reboot.rsc b/firmware-upgrade-reboot.rsc
new file mode 100644
index 0000000..038f74e
--- /dev/null
+++ b/firmware-upgrade-reboot.rsc
@@ -0,0 +1,54 @@
+#!rsc by RouterOS
+# RouterOS script: firmware-upgrade-reboot
+# Copyright (c) 2022-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# install firmware upgrade, and reboot
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/firmware-upgrade-reboot.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global LogPrint;
+ :global ScriptLock;
+ :global VersionToNum;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :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" . ".");
+ :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.");
+ :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={ }
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
new file mode 100644
index 0000000..887263e
--- /dev/null
+++ b/fw-addr-lists.rsc
@@ -0,0 +1,159 @@
+#!rsc by RouterOS
+# RouterOS script: fw-addr-lists
+# Copyright (c) 2023-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# download, import and update firewall address-lists
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/fw-addr-lists.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global FwAddrLists;
+ :global FwAddrListTimeOut;
+
+ :global CertificateAvailable;
+ :global EitherOr;
+ :global FetchUserAgent;
+ :global LogPrint;
+ :global LogPrintOnce;
+ :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;
+ }
+ }
+ }
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+ $WaitFullyConnected;
+
+ :local ListComment ("managed by " . $ScriptName);
+
+ :foreach FwListName,FwList in=$FwAddrLists do={
+ :local CntAdd 0;
+ :local CntRenew 0;
+ :local CntRemove 0;
+ :local IPv4Addresses ({});
+ :local IPv6Addresses ({});
+ :local Failure false;
+
+ :foreach List in=$FwList do={
+ :local CheckCertificate "no";
+ :local Data false;
+ :local TimeOut [ $EitherOr [ :totime ($List->"timeout") ] $FwAddrListTimeOut ];
+
+ :if ([ :len ($List->"cert") ] > 0) do={
+ :set CheckCertificate "yes-without-crl";
+ :if ([ $CertificateAvailable ($List->"cert") ] = false) do={
+ $LogPrint warning $ScriptName ("Downloading required certificate failed, trying anyway.");
+ }
+ }
+
+ :for I from=1 to=5 do={
+ :if ($Data = false) do={
+ :do {
+ :set Data ([ /tool/fetch check-certificate=$CheckCertificate output=user \
+ http-header-field=({ [ $FetchUserAgent $ScriptName ] }) ($List->"url") as-value ]->"data");
+ } on-error={
+ :if ($I < 5) do={
+ $LogPrint debug $ScriptName ("Failed downloading, " . $I . ". try: " . $List->"url");
+ :delay (($I * $I) . "s");
+ }
+ }
+ }
+ }
+
+ :if ($Data = false) do={
+ :set Data "";
+ :set Failure true;
+ $LogPrint warning $ScriptName ("Failed downloading list from: " . $List->"url");
+ }
+
+ :if ([ :len $Data ] > 63000) do={
+ $LogPrintOnce warning $ScriptName ("The list is huge and may be truncated: " . $List->"url");
+ }
+
+ :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 ($IPv4Addresses->$Address) $TimeOut;
+ }
+ :if ($Address ~ "^[0-9a-zA-Z]*:[0-9a-zA-Z:\\.]+(/[0-9]{1,3})?\$" || \
+ $Address ~ "^[\\.a-zA-Z0-9-]+\\.[a-zA-Z]{2,}\$") do={
+ :set ($IPv6Addresses->$Address) $TimeOut;
+ }
+ :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 ([ :typeof ($IPv4Addresses->$Address) ] = "time") do={
+ $LogPrint debug $ScriptName ("Renewing IPv4 address for " . ($IPv4Addresses->$Address) . ": " . $Address);
+ /ip/firewall/address-list/set $Entry timeout=($IPv4Addresses->$Address);
+ :set ($IPv4Addresses->$Address);
+ :set CntRenew ($CntRenew + 1);
+ } else={
+ :if ($Failure = false) do={
+ $LogPrint debug $ScriptName ("Removing IPv4 address: " . $Address);
+ /ip/firewall/address-list/remove $Entry;
+ :set CntRemove ($CntRemove + 1);
+ }
+ }
+ }
+
+ :foreach Entry in=[ /ipv6/firewall/address-list/find where list=$FwListName comment=$ListComment ] do={
+ :local Address [ /ipv6/firewall/address-list/get $Entry address ];
+ :if ([ :typeof ($IPv6Addresses->$Address) ] = "time") do={
+ $LogPrint debug $ScriptName ("Renewing IPv6 address for " . ($IPv6Addresses->$Address) . ": " . $Address);
+ /ipv6/firewall/address-list/set $Entry timeout=($IPv6Addresses->$Address);
+ :set ($IPv6Addresses->$Address);
+ :set CntRenew ($CntRenew + 1);
+ } else={
+ :if ($Failure = false) do={
+ $LogPrint debug $ScriptName ("Removing: " . $Address);
+ /ipv6/firewall/address-list/remove $Entry;
+ :set CntRemove ($CntRemove + 1);
+ }
+ }
+ }
+
+ :foreach Address,Timeout in=$IPv4Addresses do={
+ $LogPrint debug $ScriptName ("Adding IPv4 address for " . $Timeout . ": " . $Address);
+ :do {
+ /ip/firewall/address-list/add list=$FwListName comment=$ListComment address=$Address timeout=$Timeout;
+ :set ($IPv4Addresses->$Address);
+ :set CntAdd ($CntAdd + 1);
+ } on-error={
+ $LogPrint warning $ScriptName ("Failed to add IPv4 address " . $Address . " to list '" . $FwListName . "'.");
+ }
+ }
+
+ :foreach Address,Timeout in=$IPv6Addresses do={
+ $LogPrint debug $ScriptName ("Adding IPv6 address for " . $Timeout . ": " . $Address);
+ :do {
+ /ipv6/firewall/address-list/add list=$FwListName comment=$ListComment address=$Address timeout=$Timeout;
+ :set ($IPv6Addresses->$Address);
+ :set CntAdd ($CntAdd + 1);
+ } on-error={
+ $LogPrint warning $ScriptName ("Failed to add IPv6 address " . $Address . " to list '" . $FwListName . "'.");
+ }
+ }
+
+ $LogPrint info $ScriptName ("list: " . $FwListName . " -- added: " . $CntAdd . " - renewed: " . $CntRenew . " - removed: " . $CntRemove);
+ }
+} on-error={ }
diff --git a/global-config b/global-config
deleted file mode 100644
index 9c8dbea..0000000
--- a/global-config
+++ /dev/null
@@ -1,161 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: global-config
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# global configuration
-# https://git.eworm.de/cgit/routeros-scripts/about/
-
-# Make sure all configuration properties are up to date and this
-# value is in sync with value in script 'global-functions'!
-:global GlobalConfigVersion 47;
-
-# This is used for DNS and backup file.
-:global Domain "example.com";
-:global HostNameInZone true;
-:global PrefixInZone true;
-:global ServerNameInZone false;
-
-# These addresses are used to send e-mails to. The to-address needs
-# to be filled; cc-address can be empty, one address or a comma
-# separated list of addresses.
-:global EmailGeneralTo "";
-:global EmailGeneralCc "";
-#:global EmailGeneralTo "mail@example.com";
-#:global EmailGeneralCc "another@example.com,third@example.com";
-
-# You can send Telegram notifications. Register a bot
-# and add the token and chat ids here.
-:global TelegramTokenId "";
-:global TelegramChatId "";
-#:global TelegramTokenId "123456:ABCDEF-GHI";
-#:global TelegramChatId "12345678";
-# This is whether or not to send Telegram messages with fixed-width font.
-:global TelegramFixedWidthFont true;
-
-# Toggle this to disable symbols in notifications.
-:global NotificationsWithSymbols true;
-# Toggle this to disable color output in terminal/cli.
-:global TerminalColorOutput true;
-
-# This defines what backups to generate and what password to use.
-:global BackupSendBinary false;
-:global BackupSendExport true;
-:global BackupPassword "v3ry-s3cr3t";
-:global BackupRandomDelay 0;
-# These addresses are used to send backup and config export files to.
-:global EmailBackupTo "";
-:global EmailBackupCc "";
-# These credentials are used to upload backup and config export files.
-# SFTP authentication is tricky, you may have to limit authentication
-# methods for your SSH server.
-:global BackupUploadUrl "sftp://example.com/backup/";
-:global BackupUploadUser "mikrotik";
-:global BackupUploadPass "v3ry-s3cr3t";
-
-# This defines a filter on log topics not to be forwarded.
-:global LogForwardFilter "(debug|info)";
-# ... and the same for log message text.
-:global LogForwardFilterMessage "(^\$|^Error sending e-mail <.* Log Forwarding>:)";
-#:global LogForwardFilterMessage "(^\$|message text|...)";
-
-# Specify an address to enable auto update to version assumed safe.
-# The configured channel (bugfix, current, release-candidate) is appended.
-:global SafeUpdateUrl "";
-#:global SafeUpdateUrl "https://example.com/ros/safe-update/";
-# Allow to install patch updates automatically.
-:global SafeUpdatePatch false;
-# Allow to install updates automatically if seen in neighbor list.
-:global SafeUpdateNeighbor false;
-
-# These thresholds control when to send health notification
-# on temperature and voltage.
-:global CheckHealthTemperature {
- temperature=50;
- cpu-temperature=70;
- board-temperature1=50;
- board-temperature2=50;
-}
-# This is deviation on recovery threshold against notification flooding.
-:global CheckHealthTemperatureDeviation 2;
-:global CheckHealthVoltagePercent 10;
-
-# This controls what configuration is activated by bridge-port-to-default.
-:global BridgePortTo "default";
-
-# Access-list entries matching this comment are updated
-# with daily pseudo-random PSK.
-:global DailyPskMatchComment "Daily PSK";
-:global DailyPskSecrets {
- { "Abusive"; "Aggressive"; "Bored"; "Chemical"; "Cold";
- "Cruel"; "Curved"; "Delightful"; "Discreet"; "Elite";
- "Evasive"; "Faded"; "Flat"; "Future"; "Grandiose";
- "Hanging"; "Humorous"; "Interesting"; "Magenta";
- "Magnificent"; "Numerous"; "Optimal"; "Pathetic";
- "Possessive"; "Remarkable"; "Rightful"; "Ruthless";
- "Stale"; "Unusual"; "Useless"; "Various" };
- { "Adhesive"; "Amusing"; "Astonishing"; "Frantic";
- "Kindhearted"; "Limping"; "Roasted"; "Robust";
- "Staking"; "Thundering"; "Ultra"; "Unreal" };
- { "Belief"; "Button"; "Curtain"; "Edge"; "Jewel";
- "String"; "Whistle" }
-}
-
-# Run different commands with multiple mode-button presses.
-:global ModeButton {
- 1="/ system script run leds-toggle-mode;";
- 2=":global SendNotification; :global Identity; \$SendNotification (\"Hello...\") (\"Hello world, \" . \$Identity . \" calling!\");";
- 3="/ system shutdown;";
- 4="/ system reboot;";
- 5="/ system script run bridge-port-toggle;";
-# add more here...
-};
-# This led gives visual feedback if type is 'on' or 'off'.
-:global ModeButtonLED "user-led";
-
-# Run commands on SMS action.
-:global SmsAction {
- bridge-port-toggle="/ system script run bridge-port-toggle;";
- reboot="/ system reboot;";
- shutdown="/ system shutdown;";
-# add more here...
-};
-
-# This address should resolve ntp servers and is used to update
-# ntp settings. A pool can rotate servers.
-:global NtpPool "pool.ntp.org";
-
-# This is the address used to send gps data to.
-:global GpsTrackUrl "https://example.com/index.php";
-
-# Enable this to fetch scripts from given url.
-:global ScriptUpdatesFetch true;
-:global ScriptUpdatesBaseUrl "https://git.eworm.de/cgit/routeros-scripts/plain/";
-# alternative urls - main: stable code - next: currently in development
-#:global ScriptUpdatesBaseUrl "https://raw.githubusercontent.com/eworm-de/routeros-scripts/main/";
-#:global ScriptUpdatesBaseUrl "https://raw.githubusercontent.com/eworm-de/routeros-scripts/next/";
-#:global ScriptUpdatesBaseUrl "https://gitlab.com/eworm-de/routeros-scripts/raw/main/";
-#:global ScriptUpdatesBaseUrl "https://gitlab.com/eworm-de/routeros-scripts/raw/next/";
-:global ScriptUpdatesUrlSuffix "";
-# use next branch with default url (git.eworm.de)
-#:global ScriptUpdatesUrlSuffix "\?h=next";
-
-# This project is developed in private spare time and usage is free of charge
-# for you. If you like the scripts and think this is of value for you or your
-# business please consider a donation:
-# https://git.eworm.de/cgit/routeros-scripts/about/#donate
-# Enable this to silence donation hint.
-:global IDonate false;
-
-# Use this for certificate auto-renew
-:global CertRenewUrl "";
-#:global CertRenewUrl "https://example.com/certificates/";
-:global CertRenewTime 3w;
-:global CertRenewPass {
- "v3ry-s3cr3t";
- "4n0th3r-s3cr3t";
-}
-:global CertIssuedExportPass {
- "cert1-cn"="v3ry-s3cr3t";
- "cert2-cn"="4n0th3r-s3cr3t";
-}
diff --git a/global-config-overlay b/global-config-overlay
deleted file mode 100644
index 10376c7..0000000
--- a/global-config-overlay
+++ /dev/null
@@ -1,17 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: global-config-overlay
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# global configuration, custom overlay
-# https://git.eworm.de/cgit/routeros-scripts/about/
-
-# Make sure all configuration properties are up to date and this
-# value is in sync with value in script 'global-functions'!
-# Comment or remove to disable change notifications.
-:global GlobalConfigVersion 47;
-
-# Copy configuration from global-config here and modify it.
-
-
-# End of global-config-overlay
diff --git a/global-config-overlay.rsc b/global-config-overlay.rsc
new file mode 100644
index 0000000..9ffd90c
--- /dev/null
+++ b/global-config-overlay.rsc
@@ -0,0 +1,12 @@
+# Overlay for global configuration by RouterOS Scripts
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# global configuration, custom overlay
+# https://git.eworm.de/cgit/routeros-scripts/about/#editing-configuration
+
+# Copy relevant configuration from global-config, paste and modify it here.
+# https://git.eworm.de/cgit/routeros-scripts/about/global-config.rsc
+
+
+# End of global-config-overlay
diff --git a/global-config.changes b/global-config.changes
deleted file mode 100644
index 2bc18c0..0000000
--- a/global-config.changes
+++ /dev/null
@@ -1,60 +0,0 @@
-# RouterOS global-config changes
-# Copyright (c) 2019-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-
-# Changes for global-config to be added to notification on script updates
-:global GlobalConfigChanges {
- 1="Moved variables from 'global-config' to 'global-functions' for independence";
- 2="Variable names became CamelCase to work around scripting issues";
- 3="Variable for certificate renew passphrase became an array to support multiple passphrases";
- 4="Added option to ignore global-config changes";
- 5="Split off new script 'cloud-backup' from 'email-backup'";
- 6="Introduced script 'upload-backup' with new configuration parameters";
- 7="Introduced script 'check-health' with new configuration parameters";
- 8="Added donation hint and option to silence it";
- 9="Introduced configuration overlay 'global-config-overlay'";
- 10="Made health threshold for voltage configurable";
- 11="Introduced function '\$ScriptInstallUpdate' to install new and update existing scripts";
- 12="Removed '\$ScriptUpdatesConfigChangesIgnore', comment '\$GlobalConfigVersion' in 'global-config-overlay' to disable change notifications";
- 13="Configuration for script 'bridge-port-to-default' changed with new syntax in comment";
- 14="Dropped script 'script-updates', use '\$ScriptInstallUpdate' exclusively!";
- 15="New documentation is online! https://git.eworm.de/cgit/routeros-scripts/about/#available-scripts";
- 16="Happy with RouterOS Scripts and have a GitHub and/or GitLab account? Please star!";
- 17="Introduced script 'early-errors'";
- 18=("Added a simple IP calculation function, try: \$IPCalc " . [ / ip address get ([ find ]->0) address ]);
- 19="Commenting scripts with 'ignore', 'base-url=...' and 'url-suffix=...' is honored on update";
- 20="Added support for hooks to 'netwatch-notify'";
- 21="Added support for installing patch updates automatically by 'check-routeros-update'";
- 22="Dropped '\$ScriptUpdatesIgnore' from global configuration, auto-migrating to ignore flag in comment"
- 23="Added 'log-forward' with configurable filter, which replaces 'early-errors'";
- 24="Made symbols in notifications configurable.";
- 25="Added support for DHCP server name in DNS FQDN via '\$ServerNameInZone'";
- 26="Made check count threshold in 'netwatch-notify' configurable.";
- 27="Added queue for Telegram notifications to resend later on error.";
- 28="Made 'dhcp-to-dns' act on all bound leases, not just dynamic ones.";
- 29="Added filter on log message text for 'log-forward'.";
- 30="Implemented simple rate limit for 'log-forward' to prevent flooding.";
- 31="Switched Telegram notifications to fixed-width font, with opt-out.";
- 32="Merged mode (& reset) button scripts in single new script 'mode-button'.";
- 33="Added configurable deviation on health temperature recovery threshold against notification flooding.";
- 34="Introduced script 'ospf-to-leds' to visualize OSPF instance state via LEDs.";
- 35="Implemented visual feedback for 'mode-button' with configurable LED.";
- 36="Added support for installing updates automatically if seen in neighbor list.";
- 37="Implemented simple dependency model in 'netwatch-notify'.";
- 38="Imported new Let's Encrypt intermediate certificate 'R3'.";
- 39="Added support for interface specific address list entries in 'ipv6-update'.";
- 40="Made the certificate renewal time configurable.";
- 41="Implemented migration mechanism for script updates.";
- 42="Made severity in terminal output colorful, with opt-out.";
- 43="Added queue for e-mail notifications to resend later on error.";
- 44="Dropped script 'global-wait', all scripts wait on their own now.";
- 45="We have a Telegram Group! Come along and say hello: https://t.me/routeros_scripts";
- 46="Added configurable random delay in backup scripts to stretch execution and prevent resource congestion.";
- 47="Removed obsolete intermediate certificate 'Let's Encrypt Authority X3' from store.";
-};
-
-# 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\" ];";
-};
diff --git a/global-config.rsc b/global-config.rsc
new file mode 100644
index 0000000..f393abb
--- /dev/null
+++ b/global-config.rsc
@@ -0,0 +1,260 @@
+#!rsc by RouterOS
+# RouterOS script: global-config
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# global configuration
+# https://git.eworm.de/cgit/routeros-scripts/about/
+
+# Set this to 'true' to disable news and change notifications.
+:global NoNewsAndChangesNotification false;
+
+# Add extra text (or emojis) in notification tags.
+:global IdentityExtra "";
+
+# This is used in DNS scripts ('ipsec-to-dns' and fallback in 'dhcp-to-dns')
+# and backup scripts for file names.
+:global Domain "example.com";
+
+# You can send e-mail notifications. Configure the system's mail settings
+# (/tool/e-mail), then install the module:
+# $ScriptInstallUpdate mod/notification-email
+# The to-address needs to be filled; cc-address can be empty, one address
+# or a comma separated list of addresses.
+:global EmailGeneralTo "";
+:global EmailGeneralCc "";
+#:global EmailGeneralTo "mail@example.com";
+#:global EmailGeneralCc "another@example.com,third@example.com";
+
+# You can send Telegram notifications. Register a bot
+# and add the token and chat ids here, then install the module:
+# $ScriptInstallUpdate mod/notification-telegram
+:global TelegramTokenId "";
+:global TelegramChatId "";
+#:global TelegramTokenId "123456:ABCDEF-GHI";
+#:global TelegramChatId "12345678";
+# Using telegram-chat you have to define trusted chat ids (not group ids!)
+# or user names. Groups allow to chat with devices simultaneously.
+#:global TelegramChatIdsTrusted {
+# "12345678";
+# "example_user";
+#};
+:global TelegramChatGroups "(all)";
+#:global TelegramChatGroups "(all|home|office)";
+
+# You can send Matrix notifications. Configure these settings and
+# install the module:
+# $ScriptInstallUpdate mod/notification-matrix
+:global MatrixHomeServer "";
+:global MatrixAccessToken "";
+:global MatrixRoom "";
+#:global MatrixHomeServer "matrix.org";
+#:global MatrixAccessToken "123456ABCDEFGHI...";
+#:global MatrixRoom "!example:matrix.org";
+
+# You can send Ntfy notifications. Configure these settings and
+# install the module:
+# $ScriptInstallUpdate mod/notification-ntfy
+:global NtfyServer "ntfy.sh";
+:global NtfyTopic "";
+
+# It is possible to override e-mail, Telegram, Matrix and Ntfy setting
+# for every script. This is done in arrays, where 'Override' is appended
+# to the variable name, like this:
+#:global EmailGeneralToOverride {
+# "check-certificates"="override@example.com";
+# "backup-email"="backup@example.com";
+#}
+
+# Toggle this to disable symbols in notifications.
+:global NotificationsWithSymbols true;
+# Toggle this to disable color output in terminal/cli.
+:global TerminalColorOutput true;
+
+# This defines what backups to generate and what password to use.
+:global BackupSendBinary false;
+:global BackupSendExport true;
+:global BackupSendGlobalConfig true;
+:global BackupPassword "v3ry-s3cr3t";
+:global BackupRandomDelay 0;
+# These credentials are used to upload backup and config export files.
+# SFTP authentication is tricky, you may have to limit authentication
+# methods for your SSH server.
+:global BackupUploadUrl "sftp://example.com/backup/";
+:global BackupUploadUser "mikrotik";
+:global BackupUploadPass "v3ry-s3cr3t";
+
+# This defines the settings for firewall address-lists (fw-addr-lists).
+:global FwAddrLists {
+# "allow"={
+# { url="https://git.eworm.de/cgit/routeros-scripts/plain/fw-addr-lists.d/allow";
+# cert="E1"; timeout=1w };
+# };
+ "block"={
+# { url="https://git.eworm.de/cgit/routeros-scripts/plain/fw-addr-lists.d/block";
+# cert="E1" };
+ { 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://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" };
+ };
+# "mikrotik"={
+# { url="https://git.eworm.de/cgit/routeros-scripts/plain/fw-addr-lists.d/mikrotik";
+# cert="E1"; 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!
+# These are filters, so excluding messages from forwarding.
+: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 "account";
+#:global LogForwardIncludeMessage "message text";
+
+# Specify an address to enable auto update to version assumed safe.
+# The configured channel (bugfix, current, release-candidate) is appended.
+:global SafeUpdateUrl "";
+#:global SafeUpdateUrl "https://example.com/ros/safe-update/";
+# Allow to install patch updates automatically.
+:global SafeUpdatePatch false;
+# Allow to install updates automatically if seen in neighbor list.
+:global SafeUpdateNeighbor false;
+:global SafeUpdateNeighborIdentity "";
+# Install *ALL* updates automatically!
+# Set to all upper-case "Yes, please!" to enable.
+:global SafeUpdateAll "no";
+
+# Defer the reboot for night on automatic (non-interactive) update
+:global PackagesUpdateDeferReboot false;
+
+# These thresholds control when to send health notification
+# on temperature and voltage.
+:global CheckHealthTemperature {
+ temperature=50;
+ cpu-temperature=70;
+ board-temperature1=50;
+ board-temperature2=50;
+};
+# This is deviation on recovery threshold against notification flooding.
+:global CheckHealthTemperatureDeviation 3;
+:global CheckHealthVoltageLow 115;
+:global CheckHealthVoltagePercent 10;
+
+# Access-list entries matching this comment are updated
+# with daily pseudo-random PSK.
+:global DailyPskMatchComment "Daily PSK";
+:global DailyPskQrCodeUrl "https://www.eworm.de/cgi-bin/cqrlogo-wifi.cgi";
+:global DailyPskSecrets {
+ { "Abusive"; "Aggressive"; "Bored"; "Chemical"; "Cold";
+ "Cruel"; "Curved"; "Delightful"; "Discreet"; "Elite";
+ "Evasive"; "Faded"; "Flat"; "Future"; "Grandiose";
+ "Hanging"; "Humorous"; "Interesting"; "Magenta";
+ "Magnificent"; "Numerous"; "Optimal"; "Pathetic";
+ "Possessive"; "Remarkable"; "Rightful"; "Ruthless";
+ "Stale"; "Unusual"; "Useless"; "Various" };
+ { "Adhesive"; "Amusing"; "Astonishing"; "Frantic";
+ "Kindhearted"; "Limping"; "Roasted"; "Robust";
+ "Staking"; "Thundering"; "Ultra"; "Unreal" };
+ { "Belief"; "Button"; "Curtain"; "Edge"; "Jewel";
+ "String"; "Whistle" }
+};
+
+# Specify how to assemble DNS names in ipsec-to-dns.
+:global HostNameInZone true;
+:global PrefixInZone true;
+
+# Run different commands with multiple mode-button presses.
+:global ModeButton {
+ 1="/system/script/run leds-toggle-mode;";
+ 2=":global Identity; :global SendNotification; :global SymbolForNotification; \$SendNotification ([ \$SymbolForNotification \"earth\" ] . \"Hello...\") (\"Hello world, \" . \$Identity . \" calling!\");";
+ 3="/system/shutdown;";
+ 4="/system/reboot;";
+ 5=":global BridgePortVlan; \$BridgePortVlan alt;";
+# add more here...
+};
+# This led gives visual feedback if type is 'on' or 'off'.
+:global ModeButtonLED "user-led";
+
+# Run commands on SMS action.
+:global SmsAction {
+ bridge-port-vlan-alt=":global BridgePortVlan; \$BridgePortVlan alt;";
+ reboot="/system/reboot;";
+ shutdown="/system/shutdown;";
+# add more here...
+};
+
+# Run commands by hooking into SMS forward.
+:global SmsForwardHooks {
+ { match="magic string";
+ allowed-number="12345678";
+ command="/system/script/run ..." };
+# add more here...
+};
+
+# This is the address used to send gps data to.
+:global GpsTrackUrl "https://example.com/index.php";
+
+# This is the base url to fetch scripts from.
+:global ScriptUpdatesBaseUrl "https://git.eworm.de/cgit/routeros-scripts/plain/";
+# alternative urls - main: stable code - next: currently in development
+#:global ScriptUpdatesBaseUrl "https://raw.githubusercontent.com/eworm-de/routeros-scripts/main/";
+#:global ScriptUpdatesBaseUrl "https://raw.githubusercontent.com/eworm-de/routeros-scripts/next/";
+#:global ScriptUpdatesBaseUrl "https://gitlab.com/eworm-de/routeros-scripts/raw/main/";
+#:global ScriptUpdatesBaseUrl "https://gitlab.com/eworm-de/routeros-scripts/raw/next/";
+:global ScriptUpdatesUrlSuffix "";
+# use next branch with default url (git.eworm.de)
+#:global ScriptUpdatesUrlSuffix "?h=next";
+
+# Use this for defaults with $ScriptRunOnce
+# Install module with:
+# $ScriptInstallUpdate mod/scriptrunonce
+:global ScriptRunOnceBaseUrl "";
+:global ScriptRunOnceUrlSuffix "";
+
+# This project is developed in private spare time and usage is free of charge
+# for you. If you like the scripts and think this is of value for you or your
+# business please consider a donation:
+# https://git.eworm.de/cgit/routeros-scripts/about/#donate
+# Enable this to silence donation hint.
+:global IDonate false;
+
+# Use this for certificate auto-renew
+:global CertRenewUrl "";
+#:global CertRenewUrl "https://example.com/certificates/";
+:global CertRenewTime 3w;
+:global CertRenewPass {
+ "v3ry-s3cr3t";
+ "4n0th3r-s3cr3t";
+};
+:global CertWarnTime 2w;
+:global CertIssuedExportPass {
+ "cert1-cn"="v3ry-s3cr3t";
+ "cert2-cn"="4n0th3r-s3cr3t";
+};
+
+# load custom settings from overlay and snippets
+# Warning: Do *NOT* copy this code to overlay!
+:foreach Script in=([ /system/script/find where name="global-config-overlay" ], \
+ [ /system/script/find where name~"^global-config-overlay.d/" ]) do={
+ :do {
+ /system/script/run $Script;
+ } on-error={
+ :log error ("Loading configuration from overlay or snippet " . \
+ [ /system/script/get $Script name ] . " failed!");
+ }
+}
diff --git a/global-functions b/global-functions
deleted file mode 100644
index 7de3d72..0000000
--- a/global-functions
+++ /dev/null
@@ -1,1153 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: global-functions
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# Michael Gisbers <michael@gisbers.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# global functions
-# https://git.eworm.de/cgit/routeros-scripts/about/
-
-# expected configuration version
-:global ExpectedConfigVersion 47;
-
-# global variables not to be changed by user
-:global GlobalFunctionsReady false;
-:global Identity [ / system identity get name ];
-
-# global functions
-:global CertificateAvailable;
-:global CertificateDownload;
-:global CertificateNameByCN;
-:global CharacterReplace;
-:global CleanFilePath;
-:global DefaultRouteIsReachable;
-:global DeviceInfo;
-:global DNSIsResolving;
-:global DownloadPackage;
-:global FlushEmailQueue;
-:global FlushTelegramQueue;
-:global GetMacVendor;
-:global GetRandom20CharHex;
-:global GetRandomNumber;
-:global IfThenElse;
-:global IPCalc;
-:global LogPrintExit;
-:global LogPrintExit2;
-:global MkDir;
-:global ParseKeyValueStore;
-:global RandomDelay;
-:global RequiredRouterOS;
-:global ScriptFromTerminal;
-:global ScriptInstallUpdate;
-:global ScriptLock;
-:global SendEMail;
-:global SendNotification;
-:global SendTelegram;
-:global SymbolByUnicodeName;
-:global SymbolForNotification;
-:global TimeIsSync;
-:global UrlEncode;
-:global ValidateSyntax;
-:global VersionToNum;
-:global WaitDefaultRouteReachable;
-:global WaitDNSResolving;
-:global WaitForFile;
-:global WaitFullyConnected;
-:global WaitTimeSync;
-
-# check and download required certificate
-:set CertificateAvailable do={
- :local CommonName [ :tostr $1 ];
-
- :global CertificateDownload;
- :global LogPrintExit2;
- :global ParseKeyValueStore;
- :global RequiredRouterOS;
-
- :if ([ / system resource get free-hdd-space ] < 8388608 && \
- [ / certificate settings get crl-download ] = true && \
- [ / certificate settings get crl-store ] = "system") do={
- $LogPrintExit2 warning $0 ("This system has low free flash space but " . \
- "is configured to download certificate CRLs to system!") false;
- }
-
- :if ([ :len [ / certificate find where common-name=$CommonName ] ] = 0) do={
- $LogPrintExit2 info $0 ("Certificate with CommonName \"" . $CommonName . "\" not available.") false;
- :if ([ $CertificateDownload $CommonName ] = false) do={
- :return false;
- }
- }
-
- :if ([ $RequiredRouterOS $0 "6.47" ] = false) do={
- :return true;
- }
-
- :local CertVal [ / certificate get [ find where common-name=$CommonName ] ];
- :do {
- :if ([ :len [ / certificate find where skid=($CertVal->"akid") ] ] = 0) do={
- $LogPrintExit2 info $0 ("Certificate chain for \"" . $CommonName . \
- "\" is incomplete, missing \"" . ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") . "\".") false;
- :if ([ $CertificateDownload $CommonName ] = false) do={
- :return false;
- }
- }
- :set CertVal [ / certificate get [ find where skid=($CertVal->"akid") ] ];
- } while=(($CertVal->"akid") != "" && ($CertVal->"akid") != ($CertVal->"skid"));
- :return true;
-}
-
-# download and import certificate
-:set CertificateDownload do={
- :local CommonName [ :tostr $1 ];
-
- :global ScriptUpdatesBaseUrl;
- :global ScriptUpdatesUrlSuffix;
-
- :global CertificateNameByCN;
- :global LogPrintExit2;
- :global UrlEncode;
- :global WaitForFile;
-
- $LogPrintExit2 info $0 ("Downloading and importing certificate with " . \
- "CommonName \"" . $CommonName . "\".") false;
- :do {
- :local LocalFileName ($CommonName . ".pem");
- :local UrlFileName ([ $UrlEncode $CommonName ] . ".pem");
- / tool fetch check-certificate=yes-without-crl \
- ($ScriptUpdatesBaseUrl . "certs/" . \
- $UrlFileName . $ScriptUpdatesUrlSuffix) \
- dst-path=$LocalFileName as-value;
- $WaitForFile $LocalFileName;
- / certificate import file-name=$LocalFileName passphrase="";
- / file remove $LocalFileName;
-
- :foreach Cert in=[ / certificate find where name~("^" . $LocalFileName . "_[0-9]+\$") ] do={
- $CertificateNameByCN [ / certificate get $Cert common-name ];
- }
- } on-error={
- $LogPrintExit2 warning $0 ("Failed importing certificate with " . \
- "CommonName \"" . $CommonName . "\"!") false;
- :return false;
- }
- :return true;
-}
-
-# name a certificate by its common-name
-:set CertificateNameByCN do={
- :local CommonName [ :tostr $1 ];
-
- :global CharacterReplace;
-
- :local Cert [ / certificate find where common-name=$CommonName ];
- / certificate set $Cert \
- name=[ $CharacterReplace [ $CharacterReplace [ $CharacterReplace $CommonName "'" "-" ] " " "-" ] "---" "-" ];
-}
-
-# character replace
-:set CharacterReplace do={
- :local String [ :tostr $1 ];
- :local ReplaceFrom [ :tostr $2 ];
- :local ReplaceWith [ :tostr $3 ];
- :local Return "";
-
- :if ($ReplaceFrom = "") do={
- :return $String;
- }
-
- :while ([ :typeof [ :find $String $ReplaceFrom ] ] != "nil") do={
- :local Pos [ :find $String $ReplaceFrom ];
- :set Return ($Return . [ :pick $String 0 $Pos ] . $ReplaceWith);
- :set String [ :pick $String ($Pos + [ :len $ReplaceFrom ]) [ :len $String ] ];
- }
-
- :return ($Return . $String);
-}
-
-# clean file path
-:set CleanFilePath do={
- :local Path [ :tostr $1 ];
-
- :global CharacterReplace;
-
- :while ($Path ~ "//") do={
- :set $Path [ $CharacterReplace $Path "//" "/" ];
- }
- :if ([ :pick $Path 0 ] = "/") do={
- :set Path [ :pick $Path 1 [ :len $Path ] ];
- }
- :if ([ :pick $Path ([ :len $Path ] - 1) ] = "/") do={
- :set Path [ :pick $Path 0 ([ :len $Path ] - 1) ];
- }
-
- :return $Path;
-}
-
-# default route is reachable
-:set DefaultRouteIsReachable do={
- :if ([ :len [ / ip route find where dst-address=0.0.0.0/0 active !blackhole !routing-mark !unreachable ] ] > 0) do={
- :return true;
- }
- :return false;
-}
-
-# get readable device info
-:set DeviceInfo do={
- :global ExpectedConfigVersion;
- :global GlobalConfigVersion;
- :global Identity;
-
- :global IfThenElse;
-
- :local Resource [ / system resource get ];
- :local RouterBoard [ / system routerboard get ];
- :local Update [ / system package update get ];
-
- :return ( \
- "Hostname: " . $Identity . \
- "\nBoard name: " . $Resource->"board-name" . \
- "\nArchitecture: " . $Resource->"architecture-name" . \
- [ $IfThenElse ($RouterBoard->"routerboard" = true) \
- ("\nModel: " . $RouterBoard->"model" . \
- [ $IfThenElse ([ :len ($RouterBoard->"revision") ] > 0) \
- (" " . $RouterBoard->"revision") ] . \
- "\nSerial number: " . $RouterBoard->"serial-number") ] . \
- "\nRouterOS:" . \
- "\n Channel: " . $Update->"channel" . \
- "\n Installed: " . $Update->"installed-version" . \
- [ $IfThenElse ([ :typeof ($Update->"latest-version") ] != "nothing" && \
- $Update->"installed-version" != $Update->"latest-version") \
- ("\n Available: " . $Update->"latest-version") ] . \
- "\nRouterOS-Scripts:" . \
- "\n Current: " . $GlobalConfigVersion . \
- [ $IfThenElse ($GlobalConfigVersion != $ExpectedConfigVersion) \
- ("\n Expected: " . $ExpectedConfigVersion) ]);
-}
-
-# check if DNS is resolving
-:set DNSIsResolving do={
- :global CharacterReplace;
-
- :do {
- :resolve "low-ttl.eworm.de";
- :return true;
- } on-error={
- :return false;
- }
-}
-
-# download package from upgrade server
-:set DownloadPackage do={
- :local PkgName [ :tostr $1 ];
- :local PkgVer [ :tostr $2 ];
- :local PkgArch [ :tostr $3 ];
- :local PkgDir [ :tostr $4 ];
-
- :global CertificateAvailable;
- :global CleanFilePath;
- :global LogPrintExit2;
- :global WaitForFile;
-
- :if ([ :len $PkgName ] = 0) do={ :return false; }
- :if ([ :len $PkgVer ] = 0) do={ :set PkgVer [ / system package update get installed-version ]; }
- :if ([ :len $PkgArch ] = 0) do={ :set PkgArch [ / system resource get architecture-name ]; }
-
- :local PkgFile ($PkgName . "-" . $PkgVer . "-" . $PkgArch . ".npk");
- :if ($PkgArch = "x86_64" || $PkgName ~ "^routeros-") do={
- :set PkgFile ($PkgName . "-" . $PkgVer . ".npk");
- }
- :local PkgDest [ $CleanFilePath ($PkgDir . "/" . $PkgFile) ];
-
- :if ([ :len [ / file find where name=$PkgDest type="package" ] ] > 0) do={
- $LogPrintExit2 info $0 ("Package file alreasy exists.") false;
- :return true;
- }
-
- :if ([ $CertificateAvailable "R3" ] = false) do={
- $LogPrintExit2 error $0 ("Downloading required certificate failed.") true;
- }
-
- :local Retry 3;
- :while ($Retry > 0) do={
- :do {
- / tool fetch check-certificate=yes-without-crl \
- ("https://upgrade.mikrotik.com/routeros/" . $PkgVer . "/" . $PkgFile) \
- dst-path=$PkgDest;
- $WaitForFile $PkgDest;
-
- :if ([ / file get [ find where name=$PkgDest ] type ] = "package") do={
- :return true;
- }
- } on-error={
- $LogPrintExit2 debug $0 ("Downloading package failed.") false;
- }
-
- / file remove [ find where name=$PkgDest ];
- :set Retry ($Retry - 1);
- }
-
- :return false;
-}
-
-# flush e-mail queue
-:set FlushEmailQueue do={
- :global EmailQueue;
-
- :global LogPrintExit2;
-
- :local AllDone true;
- :local QueueLen [ :len $EmailQueue ];
-
- :if ([ :len [ / system scheduler find where name="FlushEmailQueue" ] ] > 0 && $QueueLen = 0) do={
- $LogPrintExit2 warning $0 ("Flushing E-Mail messages from scheduler, but queue is empty.") false;
- }
-
- / system scheduler set interval=1m [ find where name="FlushEmailQueue" interval=1s ];
-
- :foreach Id,Message in=$EmailQueue do={
- :if ([ :typeof $Message ] = "array" ) do={
- / tool e-mail send to=($Message->"to") cc=($Message->"cc") \
- subject=($Message->"subject") body=($Message->"body");
- :local Wait true;
- :do {
- :delay 1s;
- :local Status [ / tool e-mail get last-status ];
- :if ($Status = "succeeded") do={
- :set ($EmailQueue->$Id);
- :set Wait false;
- }
- :if ($Status = "failed") do={
- :set AllDone false;
- :set Wait false;
- }
- } while=($Wait = true);
- }
- }
-
- :if ($AllDone = true && $QueueLen = [ :len $EmailQueue ]) do={
- / system scheduler remove [ find where name="FlushEmailQueue" ];
- :set EmailQueue;
- }
-}
-
-# flush telegram queue
-:set FlushTelegramQueue do={
- :global TelegramQueue;
- :global TelegramTokenId;
-
- :global LogPrintExit2;
-
- :local AllDone true;
- :local QueueLen [ :len $TelegramQueue ];
-
- :if ([ :len [ / system scheduler find where name="FlushTelegramQueue" ] ] > 0 && $QueueLen = 0) do={
- $LogPrintExit2 warning $0 ("Flushing Telegram messages from scheduler, but queue is empty.") false;
- }
-
- :foreach Id,Message in=$TelegramQueue do={
- :if ([ :typeof $Message ] = "array" ) do={
- :do {
- / tool fetch check-certificate=yes-without-crl output=none http-method=post \
- ("https://api.telegram.org/bot" . $TelegramTokenId . "/sendMessage") \
- http-data=("chat_id=" . ($Message->"chatid") . \
- "&disable_notification=" . ($Message->"silent") . \
- "&disable_web_page_preview=true&parse_mode=" . ($Message->"parsemode") . \
- "&text=" . ($Message->"text")) as-value;
- :set ($TelegramQueue->$Id);
- } on-error={
- $LogPrintExit2 debug $0 ("Sending queued Telegram message failed.") false;
- :set AllDone false;
- }
- }
- }
-
- :if ($AllDone = true && $QueueLen = [ :len $TelegramQueue ]) do={
- / system scheduler remove [ find where name="FlushTelegramQueue" ];
- :set TelegramQueue;
- }
-}
-
-# get MAC vendor
-:set GetMacVendor do={
- :local Mac [ :tostr $1 ];
-
- :global CertificateAvailable;
- :global LogPrintExit2;
-
- :do {
- :if ([ $CertificateAvailable "Cloudflare Inc ECC CA-3" ] = false) do={
- $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true;
- }
- :local Vendor ([ / tool fetch check-certificate=yes-without-crl \
- ("https://api.macvendors.com/" . [ :pick $Mac 0 8 ]) output=user as-value ]->"data");
- :return $Vendor;
- } on-error={
- :do {
- / tool fetch check-certificate=yes-without-crl ("https://api.macvendors.com/") \
- output=none as-value;
- $LogPrintExit2 debug $0 ("The mac vendor is not known in database.") false;
- } on-error={
- $LogPrintExit2 warning $0 ("Failed getting mac vendor.") false;
- }
- :return "unknown vendor";
- }
-}
-
-# generate random 20 chars hex (0-9 and a-f)
-:set GetRandom20CharHex do={
- :local Random ([ / certificate scep-server otp generate minutes-valid=0 as-value ]->"password");
- / certificate scep-server otp remove [ find where password=$Random ];
- :return $Random;
-}
-
-# generate random number
-:set GetRandomNumber do={
- :local Max 4294967295;
- :if ([ :typeof $1 ] != "nothing" ) do={
- :set Max ([ :tonum $1 ] + 1);
- }
-
- :global GetRandom20CharHex;
-
- :local Num;
- :local 40CharHex ([ $GetRandom20CharHex ] . [ $GetRandom20CharHex ]);
-
- :for I from=0 to=39 do={
- :local Char [ :pick $40CharHex $I ];
- :if ($Char~"[0-9]") do={
- :set Num ($Num . $Char);
- }
- }
-
- :return ([ :tonum [ :pick $Num 0 18 ] ] % $Max);
-}
-
-# mimic conditional/ternary operator (condition ? consequent : alternative)
-:set IfThenElse do={
- :if ([ :tostr $1 ] = "true" || [ :tobool $1 ] = true) do={
- :return $2;
- }
- :return $3;
-}
-
-# calculate and print netmask, network, min host, max host and broadcast
-:set IPCalc do={
- :local Input [ :tostr $1 ];
- :local Address [ :toip [ :pick $Input 0 [ :find $Input "/" ] ] ];
- :local Bits [ :tonum [ :pick $Input ([ :find $Input "/" ] + 1) [ :len $Input ] ] ];
- :local Mask ((255.255.255.255 << (32 - $Bits)) & 255.255.255.255);
-
- :local Return {
- "address"=$Address;
- "netmask"=$Mask;
- "networkaddress"=($Address & $Mask);
- "networkbits"=$Bits;
- "network"=(($Address & $Mask) . "/" . $Bits);
- "hostmin"=(($Address & $Mask) | 0.0.0.1);
- "hostmax"=(($Address | ~$Mask) ^ 0.0.0.1);
- "broadcast"=($Address | ~$Mask);
- }
-
- :put ( \
- "Address: " . $Return->"address" . "\n\r" . \
- "Netmask: " . $Return->"netmask" . "\n\r" . \
- "Network: " . $Return->"network" . "\n\r" . \
- "HostMin: " . $Return->"hostmin" . "\n\r" . \
- "HostMax: " . $Return->"hostmax" . "\n\r" . \
- "Broadcast: " . $Return->"broadcast");
-
- :return $Return;
-}
-
-# deprecated compatibility wrapper
-:set LogPrintExit do={
- :global LogPrintExit2;
-
- $LogPrintExit2 $1 "unknown" $2 $3;
-}
-
-# log and print with same text, optionally exit
-:set LogPrintExit2 do={
- :local Severity [ :tostr $1 ];
- :local Name [ :tostr $2 ];
- :local Message [ :tostr $3 ];
- :local Exit [ :tostr $4 ];
-
- :global PrintDebug;
-
- :local PrintSeverity do={
- :global TerminalColorOutput;
-
- :if ($TerminalColorOutput != true) do={
- :return $1;
- }
-
- :local Color { debug=96; info=97; warning=93; error=91 };
- :return ("\1B[" . $Color->$1 . "m" . $1 . "\1B[0m");
- }
-
- :local Log ($Name . ": " . $Message);
- :if ($Severity ~ "^(debug|error|info)\$") do={
- :if ($Severity = "debug") do={ :log debug $Log; }
- :if ($Severity = "error") do={ :log error $Log; }
- :if ($Severity = "info" ) do={ :log info $Log; }
- } else={
- :log warning $Log;
- :set Severity "warning";
- }
-
- :if ($Severity != "debug" || $PrintDebug = true) do={
- :if ($Exit = "true") do={
- :error ([ $PrintSeverity $Severity ] . ": " . $Message);
- } else={
- :put ([ $PrintSeverity $Severity ] . ": " . $Message);
- }
- }
-}
-
-# create directory
-:set MkDir do={
- :local Dir [ :tostr $1 ];
-
- :global CleanFilePath;
- :global WaitForFile;
-
- :set Dir [ $CleanFilePath $Dir ];
-
- :if ([ :len [ / file find where name=$Dir type="directory" ] ] = 1) do={
- :return true;
- }
-
- :local Return true;
- :local WwwVal [ / ip service get www ];
- / ip service set www address=127.0.0.1/32 disabled=no port=80;
- :do {
- / tool fetch http://127.0.0.1/ dst-path=($Dir . "/tmp") as-value;
- $WaitForFile ($Dir . "/tmp");
- / file remove ($Dir . "/tmp");
- } on-error={
- :set Return false;
- }
- / ip service set www address=($WwwVal->"address") \
- disabled=($WwwVal->"disabled") port=($WwwVal->"port");
- :return $Return;
-}
-
-# parse key value store
-:set ParseKeyValueStore do={
- :local Source $1;
- :if ([ :typeof $Source ] != "array") do={
- :set Source [ :tostr $1 ];
- }
- :local Result [ :toarray "" ];
- :foreach KeyValue in=[ :toarray $Source ] do={
- :if ([ :find $KeyValue "=" ]) do={
- :set ($Result->[ :pick $KeyValue 0 [ :find $KeyValue "=" ] ]) \
- [ :pick $KeyValue ([ :find $KeyValue "=" ] + 1) [ :len $KeyValue ] ];
- } else={
- :set ($Result->$KeyValue) true;
- }
- }
- :return $Result;
-}
-
-# delay a random amount of seconds
-:set RandomDelay do={
- :global GetRandomNumber;
-
- :delay ([ $GetRandomNumber $1 ] . "s");
-}
-
-# check for required RouterOS version
-:set RequiredRouterOS do={
- :local Caller [ :tostr $1 ];
- :local Required [ :tostr $2 ];
-
- :global IfThenElse;
- :global LogPrintExit2;
- :global VersionToNum;
-
- :if ([ $VersionToNum $Required ] > [ $VersionToNum [ / system package update get installed-version ] ]) do={
- $LogPrintExit2 warning $0 ("This " . [ $IfThenElse ([ :pick $Caller 0 ] = "\$") "function" "script" ] . \
- " '" . $Caller . "' (at least specific functionality) requires RouterOS " . $Required . ". Please update!") false;
- :return false;
- }
- :return true;
-}
-
-# check if script is run from terminal
-:set ScriptFromTerminal do={
- :local Script [ :tostr $1 ];
-
- :global LogPrintExit2;
-
- :foreach Job in=[ / system script job find where script=$Script ] do={
- :set Job [ / system script job get $Job ];
- :while ([ :typeof ($Job->"parent") ] = "id") do={
- :set Job [ / system script job get [ find where .id=($Job->"parent") ] ];
- }
- :if (($Job->"type") = "login") do={
- $LogPrintExit2 debug $0 ("Script " . $Script . " started from terminal.") false;
- :return true;
- }
- }
- $LogPrintExit2 debug $0 ("Script " . $Script . " NOT started from terminal.") false;
-
- :return false;
-}
-
-# install new scripts, update existing scripts
-:set ScriptInstallUpdate do={
- :local Scripts [ :toarray $1 ];
-
- :global ExpectedConfigVersion;
- :global GlobalConfigVersion;
- :global Identity;
- :global IDonate;
- :global NotificationsWithSymbols;
- :global ScriptUpdatesBaseUrl;
- :global ScriptUpdatesFetch;
- :global ScriptUpdatesUrlSuffix;
- :global SentConfigChangesNotification;
-
- :global CertificateAvailable;
- :global IfThenElse;
- :global LogPrintExit2;
- :global ParseKeyValueStore;
- :global ScriptInstallUpdate;
- :global SendNotification;
- :global SymbolForNotification;
- :global ValidateSyntax;
-
- :if ([ $CertificateAvailable "R3" ] = 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 source="#!rsc by RouterOS\n";
- }
- }
-
- :local ScriptInstallUpdateBefore [ :tostr $ScriptInstallUpdate ];
-
- :foreach Script in=[ / system script find where source~"^#!rsc( by RouterOS)\?\n" ] do={
- :local ScriptVal [ / system script get $Script ];
- :local ScriptFile [ / file find where name=("script-updates/" . $ScriptVal->"name") ];
- :local SourceNew;
- :if ([ :len $ScriptFile ] > 0) do={
- :set SourceNew [ / file get $ScriptFile content ];
- / file remove $ScriptFile;
- }
-
- :foreach Scheduler in=[ / system scheduler find where on-event~("\\b" . $ScriptVal->"name" . "\\b") ] do={
- :local SchedulerVal [ / system scheduler get $Scheduler ];
- :if ($ScriptVal->"policy" != $SchedulerVal->"policy") do={
- $LogPrintExit2 warning $0 ("Policies differ for script " . $ScriptVal->"name" . \
- " and its scheduler " . $SchedulerVal->"name" . "!") false;
- }
- }
-
- :if ([ :len $SourceNew ] = 0 && $ScriptUpdatesFetch = true) do={
- :local Comment [ $ParseKeyValueStore ($ScriptVal->"comment") ];
- :if (!($Comment->"ignore" = true)) do={
- $LogPrintExit2 debug $0 ("Fetching script from url: " . $ScriptVal->"name") false;
- :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 Result [ / tool fetch check-certificate=yes-without-crl \
- ($BaseUrl . $ScriptVal->"name" . $UrlSuffix) output=user as-value ];
- :if ($Result->"status" = "finished") do={
- :set SourceNew ($Result->"data");
- }
- } on-error={
- $LogPrintExit2 warning $0 ("Failed fetching " . $ScriptVal->"name") false;
- }
- }
- }
-
- :if ([ :len $SourceNew ] > 0) do={
- :if ($SourceNew != $ScriptVal->"source") do={
- :if ([ :pick $SourceNew 0 18 ] = "#!rsc by RouterOS\n") do={
- :if ([ $ValidateSyntax $SourceNew ] = true) do={
- :local DontRequirePermissions \
- ($SourceNew~"\n# requires: dont-require-permissions=yes\n");
- $LogPrintExit2 info $0 ("Updating script: " . $ScriptVal->"name") false;
- / system script set owner=($ScriptVal->"name") source=$SourceNew \
- dont-require-permissions=$DontRequirePermissions $Script;
- :if ($ScriptVal->"name" = "global-config") do={
- $LogPrintExit2 info $0 ("Reloading global configuration and overlay.") false;
- :do {
- / system script { run global-config; run global-config-overlay; }
- } on-error={
- $LogPrintExit2 error $0 ("Reloading global configuration and overlay failed!" . \
- " Syntax error or missing overlay\?") false;
- }
- }
- :if ($ScriptVal->"name" = "global-functions") do={
- $LogPrintExit2 info $0 ("Reloading global functions.") false;
- :do {
- / system script run global-functions;
- } on-error={
- $LogPrintExit2 error $0 ("Reloading global functions failed!") false;
- }
- }
- } else={
- $LogPrintExit2 warning $0 ("Syntax validation for script " . $ScriptVal->"name" . \
- " failed! Ignoring!") false;
- }
- } else={
- $LogPrintExit2 warning $0 ("Looks like new script " . $ScriptVal->"name" . \
- " is not valid (missing shebang). Ignoring!") false;
- }
- } else={
- $LogPrintExit2 debug $0 ("Script " . $ScriptVal->"name" . " did not change.") false;
- }
- } else={
- $LogPrintExit2 debug $0 ("No update for script " . $ScriptVal->"name" . ".") false;
- }
- }
-
- :if ($SentConfigChangesNotification!=$ExpectedConfigVersion && \
- $GlobalConfigVersion < $ExpectedConfigVersion) do={
- :global GlobalConfigChanges;
- :global GlobalConfigMigration;
- :local ChangeLogCode;
- :local NotificationMessage ("Current configuration on " . $Identity . \
- " is out of date. Please update global-config-overlay, then increase " . \
- "\$GlobalConfigVersion (currently " . $GlobalConfigVersion . \
- ") to " . $ExpectedConfigVersion . " and re-run global-config-overlay.");
- $LogPrintExit2 info $0 ($NotificationMessage) false;
-
- $LogPrintExit2 debug $0 ("Fetching changelog.") false;
- :do {
- :local Result [ / tool fetch check-certificate=yes-without-crl \
- ($ScriptUpdatesBaseUrl . "global-config.changes" . $ScriptUpdatesUrlSuffix) \
- output=user as-value ];
- :if ($Result->"status" = "finished") do={
- :set ChangeLogCode ($Result->"data");
- }
- } on-error={
- $LogPrintExit2 warning $0 ("Failed fetching changes!") false;
- :set NotificationMessage ($NotificationMessage . \
- "\n\nChanges are not available.");
- }
-
- :if ([ :len $ChangeLogCode ] > 0) do={
- :if ([ $ValidateSyntax $ChangeLogCode ] = true) do={
- :set NotificationMessage ($NotificationMessage . "\n\nChanges:");
- [ :parse $ChangeLogCode ];
- :for I from=($GlobalConfigVersion + 1) to=$ExpectedConfigVersion do={
- :local Migration ($GlobalConfigMigration->[ :tostr $I ]);
- :if ([ :typeof $Migration ] = "str") do={
- :if ([ $ValidateSyntax $Migration ] = true) do={
- $LogPrintExit2 info $0 ("Applying migration: " . $Migration) false;
- [ :parse $Migration ];
- } else={
- $LogPrintExit2 warning $0 ("Migration code for change " . $I . " failed syntax validation!") false;
- }
- }
- :set NotificationMessage ($NotificationMessage . \
- "\n " . [ $IfThenElse ($NotificationsWithSymbols = true) ("\E2\97\8F") "*" ] . " " . \
- $GlobalConfigChanges->[ :tostr $I ]);
- $LogPrintExit2 info $0 ("Change: " . $GlobalConfigChanges->[ :tostr $I ]) false;
- }
- :set GlobalConfigChanges;
- :set GlobalConfigMigration;
- } else={
- $LogPrintExit2 warning $0 ("The changelog failed syntax validation!") false;
- :set NotificationMessage ($NotificationMessage . \
- "\n\nChanges are not available.");
- }
- }
-
- :local Link;
- :if ($IDonate != true) do={
- :set NotificationMessage ($NotificationMessage . \
- "\n\n==== donation hint ====\n" . \
- "This project is developed in private spare time and usage is " . \
- "free of charge for you. If you like the scripts and think this is " . \
- "of value for you or your business please consider a donation.");
- :set Link "https://git.eworm.de/cgit/routeros-scripts/about/#donate";
- }
-
- $SendNotification ([ $SymbolForNotification "pushpin" ] . "News and configuration changes") \
- $NotificationMessage $Link;
- :set SentConfigChangesNotification $ExpectedConfigVersion;
- }
-
- :if ($ScriptInstallUpdateBefore != [ :tostr $ScriptInstallUpdate ]) do={
- $LogPrintExit2 info $0 ("This function changed, you may want to re-run.") false;
- }
-}
-
-# lock script against multiple invocation
-:set ScriptLock do={
- :global LogPrintExit2;
-
- :local Script [ :tostr $1 ];
-
- :if ([ :len [ / system script job find where script=$Script ] ] > 1) do={
- $LogPrintExit2 info $0 ("Script " . $Script . " started more than once... Aborting.") true;
- }
-}
-
-# send notification via e-mail
-:set SendEMail do={
- :local Subject [ :tostr $1 ];
- :local Message [ :tostr $2 ];
- :local Link [ :tostr $3 ];
-
- :global Identity;
- :global EmailGeneralTo;
- :global EmailGeneralCc;
- :global EmailQueue;
-
- :global LogPrintExit2;
- :global IfThenElse;
-
- :if ([ :len $EmailGeneralTo ] = 0) do={
- :return false;
- }
-
- :if ([ :typeof $EmailQueue ] = "nothing") do={
- :set EmailQueue [ :toarray "" ];
- }
- :local Signature [ / system note get note ];
- :set ($EmailQueue->[ :len $EmailQueue ]) {
- to=$EmailGeneralTo; cc=$EmailGeneralCc; subject=("[" . $Identity . "] " . $Subject);
- body=($Message . \
- [ $IfThenElse ([ :len $Link ] > 0) ("\n\n" . $Link) "" ] . \
- [ $IfThenElse ([ :len $Signature ] > 0) ("\n-- \n" . $Signature) "" ]) };
- :if ([ :len [ / system scheduler find where name="FlushEmailQueue" ] ] = 0) do={
- / system scheduler add name=FlushEmailQueue interval=1s start-time=startup \
- on-event=":global FlushEmailQueue; \$FlushEmailQueue;";
- }
-}
-
-# send notification via e-mail and telegram
-# Note that attachment is ignored for telegram, silent is ignored for e-mail!
-:set SendNotification do={
- :local Subject [ :tostr $1 ];
- :local Message [ :tostr $2 ];
- :local Link [ :tostr $3 ];
- :local Silent [ :tostr $4 ];
-
- :global SendEMail;
- :global SendTelegram;
-
- $SendEMail $Subject $Message $Link;
- $SendTelegram $Subject $Message $Link $Silent;
-}
-
-# send notification via telegram
-:set SendTelegram do={
- :local Subject [ :tostr $1 ];
- :local Message [ :tostr $2 ];
- :local Link [ :tostr $3 ];
- :local Silent [ :tostr $4 ];
-
- :global Identity;
- :global TelegramChatId;
- :global TelegramChatIdOverride;
- :global TelegramFixedWidthFont;
- :global TelegramQueue;
- :global TelegramTokenId;
-
- :global CertificateAvailable;
- :global CharacterReplace;
- :global IfThenElse;
- :global LogPrintExit2;
- :global SymbolForNotification;
- :global UrlEncode;
-
- :local EscapeMD do={
- :global TelegramFixedWidthFont;
-
- :global CharacterReplace;
- :global IfThenElse;
-
- :if ($TelegramFixedWidthFont != true) do={
- :return ($1 . [ $IfThenElse ($2 = "body") "\n" "" ]);
- }
-
- :local Return $1;
- :local Chars {
- "body"={ "\\"; "`" };
- "hint"={ "_"; "*"; "["; "]"; "("; ")"; "~"; "`"; ">";
- "#"; "+"; "-"; "="; "|"; "{"; "}"; "."; "!" };
- }
- :foreach Char in=($Chars->$2) do={
- :set Return [ $CharacterReplace $Return $Char ("\\" . $Char) ];
- }
-
- :if ($2 = "body") do={
- :return ("```\n" . $Return . "\n```");
- }
-
- :return $Return;
- }
-
- :local ChatId $TelegramChatId;
- :if ([ :len $TelegramChatIdOverride ] > 0) do={
- :set ChatId $TelegramChatIdOverride;
- }
-
- :if ([ :len $TelegramTokenId ] = 0 || [ :len $ChatId ] = 0) do={
- :return false;
- }
-
- :local Truncated false;
- :local LenLink [ :len $Link ];
- :local Text ("[" . $Identity . "] " . $Subject . "\n\n" . $Message);
- :local LenText [ :len $Text ];
- :if ($LenText > (3968 - $LenLink)) do={
- :set Text [ $EscapeMD ([ :pick $Text 0 (3840 - $LenLink) ] . "...") "body" ];
- :set Truncated true;
- } else={
- :set Text [ $EscapeMD $Text "body" ];
- }
- :if ($LenLink > 0) do={
- :set Text ($Text . "\n" . [ $SymbolForNotification "link" ] . [ $EscapeMD $Link "hint" ]);
- }
- :if ($Truncated = true) do={
- :set Text ($Text . "\n" . [ $SymbolForNotification "scissors" ] . \
- [ $EscapeMD ("The Telegram message was too long and has been truncated, cut off " . \
- (($LenText - [ :len $Text ]) * 100 / $LenText) . "%!") "hint" ]);
- }
- :set Text [ $UrlEncode $Text ];
- :local ParseMode [ $IfThenElse ($TelegramFixedWidthFont = true) "MarkdownV2" "" ];
-
- :do {
- :if ([ $CertificateAvailable "Go Daddy Secure Certificate Authority - G2" ] = false) do={
- $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true;
- }
- / tool fetch check-certificate=yes-without-crl output=none http-method=post \
- ("https://api.telegram.org/bot" . $TelegramTokenId . "/sendMessage") \
- http-data=("chat_id=" . $ChatId . "&disable_notification=" . $Silent . \
- "&disable_web_page_preview=true&parse_mode=" . $ParseMode . "&text=" . $Text) as-value;
- } on-error={
- $LogPrintExit2 info $0 ("Failed sending telegram notification! Queuing...") false;
-
- :if ([ :typeof $TelegramQueue ] = "nothing") do={
- :set TelegramQueue [ :toarray "" ];
- }
- :set Text ($Text . [ $UrlEncode ("\n" . [ $SymbolForNotification "alarm-clock" ] . \
- [ $EscapeMD ("This message was queued since " . [ / system clock get date ] . \
- " " . [ / system clock get time ] . " and may be obsolete.") "hint" ]) ]);
- :set ($TelegramQueue->[ :len $TelegramQueue ]) {
- chatid=$ChatId; parsemode=$ParseMode; text=$Text; silent=$Silent };
- :if ([ :len [ / system scheduler find where name="FlushTelegramQueue" ] ] = 0) do={
- / system scheduler add name=FlushTelegramQueue interval=1m start-time=startup \
- on-event=":global FlushTelegramQueue; \$FlushTelegramQueue;";
- }
- }
-}
-
-# return UTF-8 symbol for unicode name
-:set SymbolByUnicodeName do={
- :local Symbols {
- "alarm-clock"="\E2\8F\B0";
- "calendar"="\F0\9F\93\85";
- "cross-mark"="\E2\9D\8C";
- "fire"="\F0\9F\94\A5";
- "floppy-disk"="\F0\9F\92\BE";
- "high-voltage-sign"="\E2\9A\A1";
- "incoming-envelope"="\F0\9F\93\A8";
- "link"="\F0\9F\94\97";
- "lock-with-ink-pen"="\F0\9F\94\8F";
- "mobile-phone"="\F0\9F\93\B1";
- "pushpin"="\F0\9F\93\8C";
- "scissors"="\E2\9C\82";
- "sparkles"="\E2\9C\A8";
- "warning-sign"="\E2\9A\A0";
- "white-heavy-check-mark"="\E2\9C\85"
- }
-
- :return ($Symbols->$1);
-}
-
-# return symbol for notification
-:set SymbolForNotification do={
- :global NotificationsWithSymbols;
- :global SymbolByUnicodeName;
-
- :if ($NotificationsWithSymbols != true) do={
- :return "";
- }
- :local Return "";
- :foreach Symbol in=[ :toarray $1 ] do={
- :set Return ($Return . [ $SymbolByUnicodeName $Symbol ]);
- }
- :return ($Return . " ");
-}
-
-# check if system time is sync
-:set TimeIsSync do={
- :global LogPrintExit2;
-
- :if ([ / system ntp client get enabled ] = true) do={
- :do {
- :if ([ / system ntp client get status ] = "synchronized") do={
- :return true;
- }
- } on-error={
- :if ([ :typeof [ / system ntp client get last-adjustment ] ] = "time") do={
- :return true;
- }
- }
- :return false;
- }
-
- :if ([ / ip cloud get ddns-enabled ] = true && [ / ip cloud get update-time ] = true) do={
- :if ([ :typeof [ / ip cloud get public-address ] ] = "ip") do={
- :return true;
- }
- :return false;
- }
-
- $LogPrintExit2 debug $0 ("No time source configured! Returning gracefully...") false;
- :return true;
-}
-
-# url encoding
-:set UrlEncode do={
- :local Input [ :tostr $1 ];
- :local Return "";
-
- :if ([ :len $Input ] > 0) do={
- :local Chars "\n\r !\"#\$%&'()*+,:;<=>\?@[\\]^`{|}~";
- :local Subs { "%0A"; "%0D"; "%20"; "%21"; "%22"; "%23"; "%24"; "%25"; "%26"; "%27";
- "%28"; "%29"; "%2A"; "%2B"; "%2C"; "%3A"; "%3B"; "%3C"; "%3D"; "%3E";
- "%3F"; "%40"; "%5B"; "%5C"; "%5D"; "%5E"; "%60"; "%7B"; "%7C"; "%7D";
- "%7E" };
-
- :for I from=0 to=([ :len $Input ] - 1) do={
- :local Char [ :pick $Input $I ];
- :local Replace [ :find $Chars $Char ];
-
- :if ([ :len $Replace ] > 0) do={
- :set Char ($Subs->$Replace);
- }
- :set Return ($Return . $Char);
- }
- }
-
- :return $Return;
-}
-
-# basic syntax validation
-:set ValidateSyntax do={
- :local Code [ :tostr $1 ];
-
- :do {
- [ :parse (":local Validate do={ " . $Code . " }") ];
- } on-error={
- :return false;
- }
- :return true;
-}
-
-# convert version string to numeric value
-:set VersionToNum do={
- :local Input [ :tostr $1 ];
- :local Multi 0x1000000;
- :local Return 0;
-
- :global CharacterReplace;
-
- :set Input [ $CharacterReplace [ $CharacterReplace [ $CharacterReplace $Input \
- "." "," ] "beta" ",beta," ] "rc" ",rc," ];
-
- :foreach Value in=([ :toarray $Input ], 0) do={
- :local Num [ :tonum $Value ];
- :if ($Multi = 0x100) do={
- :if ([ :typeof $Num ] = "num") do={
- :set Return ($Return + 0xff00);
- :set Multi ($Multi / 0x100);
- } else={
- :if ($Value = "beta") do={ :set Return ($Return + 0x3f00); }
- :if ($Value = "rc") do={ :set Return ($Return + 0x7f00); }
- }
- }
- :if ([ :typeof $Num ] = "num") do={ :set Return ($Return + ($Value * $Multi)); }
- :set Multi ($Multi / 0x100);
- }
-
- :return $Return;
-}
-
-# wait for default route to be reachable
-:set WaitDefaultRouteReachable do={
- :global DefaultRouteIsReachable;
-
- :while ([ $DefaultRouteIsReachable ] = false) do={
- :delay 1s;
- }
-}
-
-# wait for DNS to resolve
-:set WaitDNSResolving do={
- :global DNSIsResolving;
-
- :while ([ $DNSIsResolving ] = false) do={
- :delay 1s;
- }
-}
-
-# wait for file to be available
-:set WaitForFile do={
- :local FileName [ :tostr $1 ];
-
- :global CleanFilePath;
-
- :set FileName [ $CleanFilePath $FileName ];
- :local I 0;
-
- :while ([ :len [ / file find where name=$FileName ] ] = 0) do={
- :if ($I > 20) do={
- :return false;
- }
- :delay 100ms;
- :set I ($I + 1);
- }
- :return true;
-}
-
-# wait to be fully connected (default route is reachable, time is sync, DNS resolves)
-:set WaitFullyConnected do={
- :global WaitDefaultRouteReachable;
- :global WaitDNSResolving;
- :global WaitTimeSync;
-
- $WaitDefaultRouteReachable;
- $WaitTimeSync;
- $WaitDNSResolving;
-}
-
-# wait for time to become synced
-:set WaitTimeSync do={
- :global LogPrintExit2;
- :global TimeIsSync;
-
- :while ([ $TimeIsSync ] = false) do={
- :if ([ :len [ / system script find where name="rotate-ntp" ] ] > 0 && \
- ([ / system resource get uptime ] % (180 * 1000000000)) = 0s) do={
- :do {
- / system script run rotate-ntp;
- } on-error={
- $LogPrintExit2 debug $0 ("Running rotate-ntp failed.") false;
- }
- }
- :delay 1s;
- }
-}
-
-# check for required RouterOS version
-$RequiredRouterOS "global-functions" "6.47";
-
-# signal we are ready
-:set GlobalFunctionsReady true;
diff --git a/global-functions.rsc b/global-functions.rsc
new file mode 100644
index 0000000..17ccda8
--- /dev/null
+++ b/global-functions.rsc
@@ -0,0 +1,1578 @@
+#!rsc by RouterOS
+# RouterOS script: global-functions
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# Michael Gisbers <michael@gisbers.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# global functions
+# https://git.eworm.de/cgit/routeros-scripts/about/
+
+:local ScriptName [ :jobname ];
+
+# expected configuration version
+:global ExpectedConfigVersion 124;
+
+# global variables not to be changed by user
+:global GlobalFunctionsReady false;
+:global Identity [ /system/identity/get name ];
+
+# global functions
+:global AlignRight;
+:global CertificateAvailable;
+:global CertificateDownload;
+:global CertificateNameByCN;
+:global CharacterMultiply;
+:global CharacterReplace;
+:global CleanFilePath;
+:global CleanName;
+:global DeviceInfo;
+:global Dos2Unix;
+:global DownloadPackage;
+:global EitherOr;
+:global EscapeForRegEx;
+:global FetchUserAgent;
+:global FormatLine;
+:global FormatMultiLines;
+:global GetMacVendor;
+:global GetRandom20CharAlNum;
+:global GetRandom20CharHex;
+:global GetRandomNumber;
+:global Grep;
+:global HexToNum;
+:global HumanReadableNum;
+:global IfThenElse;
+:global IsDefaultRouteReachable;
+:global IsDNSResolving;
+:global IsFullyConnected;
+:global IsMacLocallyAdministered;
+:global IsTimeSync;
+:global LogPrint;
+:global LogPrintExit2;
+:global LogPrintOnce;
+:global MAX;
+:global MIN;
+:global MkDir;
+:global NotificationFunctions;
+:global ParseDate;
+:global ParseJson;
+:global ParseKeyValueStore;
+:global PrettyPrint;
+:global RandomDelay;
+:global RequiredRouterOS;
+:global ScriptFromTerminal;
+:global ScriptInstallUpdate;
+:global ScriptLock;
+:global SendNotification;
+:global SendNotification2;
+:global SymbolByUnicodeName;
+:global SymbolForNotification;
+:global Unix2Dos;
+:global UrlEncode;
+:global ValidateSyntax;
+:global VersionToNum;
+:global WaitDefaultRouteReachable;
+:global WaitDNSResolving;
+:global WaitForFile;
+:global WaitFullyConnected;
+:global WaitTimeSync;
+
+# align string to the right
+:set AlignRight do={
+ :local Input [ :tostr $1 ];
+ :local Len [ :tonum $2 ];
+
+ :global CharacterMultiply;
+ :global EitherOr;
+
+ :set Len [ $EitherOr $Len 8 ];
+ :local Spaces [ $CharacterMultiply " " $Len ];
+
+ :return ([ :pick $Spaces 0 ($Len - [ :len $Input ]) ] . $Input);
+}
+
+# check and download required certificate
+:set CertificateAvailable do={
+ :local CommonName [ :tostr $1 ];
+
+ :global CertificateDownload;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+
+ :if ([ /system/resource/get free-hdd-space ] < 8388608 && \
+ [ /certificate/settings/get crl-download ] = true && \
+ [ /certificate/settings/get crl-store ] = "system") do={
+ $LogPrint warning $0 ("This system has low free flash space but " . \
+ "is configured to download certificate CRLs to system!");
+ }
+
+ :if ([ :len [ /certificate/find where common-name=$CommonName ] ] = 0) do={
+ $LogPrint info $0 ("Certificate with CommonName \"" . $CommonName . "\" not available.");
+ :if ([ $CertificateDownload $CommonName ] = false) do={
+ :return false;
+ }
+ }
+
+ :local CertVal [ /certificate/get [ find where common-name=$CommonName ] ];
+ :while (($CertVal->"akid") != "" && ($CertVal->"akid") != ($CertVal->"skid")) do={
+ :if ([ :len [ /certificate/find where skid=($CertVal->"akid") ] ] = 0) do={
+ $LogPrint info $0 ("Certificate chain for \"" . $CommonName . \
+ "\" is incomplete, missing \"" . ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") . "\".");
+ :if ([ $CertificateDownload $CommonName ] = false) do={
+ :return false;
+ }
+ }
+ :set CertVal [ /certificate/get [ find where skid=($CertVal->"akid") ] ];
+ }
+ :return true;
+}
+
+# download and import certificate
+:set CertificateDownload do={
+ :local CommonName [ :tostr $1 ];
+
+ :global ScriptUpdatesBaseUrl;
+ :global ScriptUpdatesUrlSuffix;
+
+ :global CertificateNameByCN;
+ :global CleanName;
+ :global FetchUserAgent;
+ :global LogPrint;
+ :global WaitForFile;
+
+ $LogPrint info $0 ("Downloading and importing certificate with " . \
+ "CommonName \"" . $CommonName . "\".");
+ :do {
+ :local FileName ([ $CleanName $CommonName ] . ".pem");
+ /tool/fetch check-certificate=yes-without-crl http-header-field=({ [ $FetchUserAgent $0 ] }) \
+ ($ScriptUpdatesBaseUrl . "certs/" . $FileName . $ScriptUpdatesUrlSuffix) \
+ dst-path=$FileName as-value;
+ $WaitForFile $FileName;
+ /certificate/import file-name=$FileName passphrase="" as-value;
+ :delay 1s;
+ /file/remove $FileName;
+
+ :foreach Cert in=[ /certificate/find where name~("^" . $FileName . "_[0-9]+\$") ] do={
+ $CertificateNameByCN [ /certificate/get $Cert common-name ];
+ }
+ } on-error={
+ $LogPrint warning $0 ("Failed importing certificate with CommonName \"" . $CommonName . "\"!");
+ :return false;
+ }
+ :return true;
+}
+
+# name a certificate by its common-name
+:set CertificateNameByCN do={
+ :local CommonName [ :tostr $1 ];
+
+ :global CleanName;
+
+ :local Cert [ /certificate/find where common-name=$CommonName ];
+ /certificate/set $Cert name=[ $CleanName $CommonName ];
+}
+
+# multiply given character(s)
+:set CharacterMultiply do={
+ :local Return "";
+ :for I from=1 to=$2 do={
+ :set Return ($Return . $1);
+ }
+ :return $Return;
+}
+
+# character replace
+:set CharacterReplace do={
+ :local String [ :tostr $1 ];
+ :local ReplaceFrom [ :tostr $2 ];
+ :local ReplaceWith [ :tostr $3 ];
+ :local Return "";
+
+ :if ($ReplaceFrom = "") do={
+ :return $String;
+ }
+
+ :while ([ :typeof [ :find $String $ReplaceFrom ] ] != "nil") do={
+ :local Pos [ :find $String $ReplaceFrom ];
+ :set Return ($Return . [ :pick $String 0 $Pos ] . $ReplaceWith);
+ :set String [ :pick $String ($Pos + [ :len $ReplaceFrom ]) [ :len $String ] ];
+ }
+
+ :return ($Return . $String);
+}
+
+# clean file path
+:set CleanFilePath do={
+ :local Path [ :tostr $1 ];
+
+ :global CharacterReplace;
+
+ :while ($Path ~ "//") do={
+ :set $Path [ $CharacterReplace $Path "//" "/" ];
+ }
+ :if ([ :pick $Path 0 ] = "/") do={
+ :set Path [ :pick $Path 1 [ :len $Path ] ];
+ }
+ :if ([ :pick $Path ([ :len $Path ] - 1) ] = "/") do={
+ :set Path [ :pick $Path 0 ([ :len $Path ] - 1) ];
+ }
+
+ :return $Path;
+}
+
+# clean name for DNS, file and more
+:set CleanName do={
+ :local Input [ :tostr $1 ];
+
+ :local Return "";
+
+ :for I from=0 to=([ :len $Input ] - 1) do={
+ :local Char [ :pick $Input $I ];
+ :if ([ :typeof [ find "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-" $Char ] ] = "nil") do={
+ :set Char "-";
+ }
+ :if ($Char != "-" || [ :pick $Return ([ :len $Return ] - 1) ] != "-") do={
+ :set Return ($Return . $Char);
+ }
+ }
+ :return $Return;
+}
+
+# get readable device info
+:set DeviceInfo do={
+ :global ExpectedConfigVersion;
+ :global Identity;
+
+ :global IfThenElse;
+ :global FormatLine;
+
+ :local License [ /system/license/get ];
+ :local Resource [ /system/resource/get ];
+ :local RouterBoard;
+ :do {
+ :set RouterBoard [[ :parse "/system/routerboard/get" ]];
+ } on-error={ }
+ :local Snmp [ /snmp/get ];
+ :local Update [ /system/package/update/get ];
+
+ :return ( \
+ [ $FormatLine "Hostname" $Identity ] . "\n" . \
+ [ $IfThenElse ([ :len ($Snmp->"location") ] > 0) \
+ ([ $FormatLine "Location" ($Snmp->"location") ] . "\n") ] . \
+ [ $IfThenElse ([ :len ($Snmp->"contact") ] > 0) \
+ ([ $FormatLine "Contact" ($Snmp->"contact") ] . "\n") ] . \
+ [ $FormatLine "Board name" ($Resource->"board-name") ] . "\n" . \
+ [ $FormatLine "Architecture" ($Resource->"architecture-name") ] . "\n" . \
+ [ $IfThenElse ($RouterBoard->"routerboard" = true) \
+ ([ $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") ] . \
+ "RouterOS:\n" . \
+ [ $FormatLine " Channel" ($Update->"channel") ] . "\n" . \
+ [ $FormatLine " Installed" ($Update->"installed-version") ] . "\n" . \
+ [ $IfThenElse ([ :typeof ($Update->"latest-version") ] != "nothing" && \
+ $Update->"installed-version" != $Update->"latest-version") \
+ ([ $FormatLine " Available" ($Update->"latest-version") ] . "\n") ] . \
+ [ $IfThenElse ($RouterBoard->"routerboard" = true && \
+ $RouterBoard->"current-firmware" != $RouterBoard->"upgrade-firmware") \
+ ([ $FormatLine " Firmware" ($RouterBoard->"current-firmware") ] . "\n") ] . \
+ "RouterOS-Scripts:\n" . \
+ [ $FormatLine " Version" $ExpectedConfigVersion ]);
+}
+
+# convert line endings, DOS -> UNIX
+:set Dos2Unix do={
+ :local Input [ :tostr $1 ];
+
+ :global CharacterReplace;
+
+ :return [ $CharacterReplace $Input ("\r\n") ("\n") ];
+}
+
+# download package from upgrade server
+:set DownloadPackage do={
+ :local PkgName [ :tostr $1 ];
+ :local PkgVer [ :tostr $2 ];
+ :local PkgArch [ :tostr $3 ];
+ :local PkgDir [ :tostr $4 ];
+
+ :global CertificateAvailable;
+ :global CleanFilePath;
+ :global LogPrint;
+ :global MkDir;
+ :global WaitForFile;
+
+ :if ([ :len $PkgName ] = 0) do={ :return false; }
+ :if ([ :len $PkgVer ] = 0) do={ :set PkgVer [ /system/package/update/get installed-version ]; }
+ :if ([ :len $PkgArch ] = 0) do={ :set PkgArch [ /system/resource/get architecture-name ]; }
+
+ :if ($PkgName = "system") do={ :set PkgName "routeros"; }
+
+ :local PkgFile ($PkgName . "-" . $PkgVer . "-" . $PkgArch . ".npk");
+ :if ($PkgArch = "x86_64") do={ :set PkgFile ($PkgName . "-" . $PkgVer . ".npk"); }
+ :local PkgDest [ $CleanFilePath ($PkgDir . "/" . $PkgFile) ];
+
+ :if ([ $MkDir $PkgDir ] = false) do={
+ $LogPrint warning $0 ("Failed creating directory, not downloading package.");
+ :return false;
+ }
+
+ :if ([ :len [ /file/find where name=$PkgDest type="package" ] ] > 0) do={
+ $LogPrint info $0 ("Package file " . $PkgName . " already exists.");
+ :return true;
+ }
+
+ :if ([ $CertificateAvailable "R3" ] = false) do={
+ $LogPrint error $0 ("Downloading required certificate failed.");
+ :return false;
+ }
+
+ :local Url ("https://upgrade.mikrotik.com/routeros/" . $PkgVer . "/" . $PkgFile);
+ $LogPrint info $0 ("Downloading package file '" . $PkgName . "'...");
+ $LogPrint debug $0 ("... from url: " . $Url);
+ :local Retry 3;
+ :while ($Retry > 0) do={
+ :do {
+ /tool/fetch check-certificate=yes-without-crl $Url dst-path=$PkgDest;
+ $WaitForFile $PkgDest;
+
+ :if ([ /file/get [ find where name=$PkgDest ] type ] = "package") do={
+ :return true;
+ }
+ } on-error={
+ $LogPrint debug $0 ("Downloading package file failed.");
+ }
+
+ /file/remove [ find where name=$PkgDest ];
+ :set Retry ($Retry - 1);
+ }
+
+ $LogPrint warning $0 ("Downloading package file '" . $PkgName . "' failed.");
+ :return false;
+}
+
+# return either first (if "true") or second
+:set EitherOr do={
+ :global IfThenElse;
+
+ :if ([ :typeof $1 ] = "num") do={
+ :return [ $IfThenElse ($1 != 0) $1 $2 ];
+ }
+ :if ([ :typeof $1 ] = "time") do={
+ :return [ $IfThenElse ($1 > 0s) $1 $2 ];
+ }
+ :return [ $IfThenElse ([ :len [ :tostr $1 ] ] > 0) $1 $2 ];
+}
+
+# escape for regular expression
+:set EscapeForRegEx do={
+ :local Input [ :tostr $1 ];
+
+ :if ([ :len $Input ] = 0) do={
+ :return "";
+ }
+
+ :local Return "";
+ :local Chars ("^.[]\$()|*+?{}\\");
+
+ :for I from=0 to=([ :len $Input ] - 1) do={
+ :local Char [ :pick $Input $I ];
+ :if ([ :find $Chars $Char ]) do={
+ :set Char ("\\" . $Char);
+ }
+ :set Return ($Return . $Char);
+ }
+
+ :return $Return;
+}
+
+# generate user agent string for fetch
+:set FetchUserAgent do={
+ :local Caller [ :tostr $1 ];
+
+ :local Resource [ /system/resource/get ];
+
+ :return ("User-Agent: Mikrotik/" . $Resource->"version" . " " . \
+ $Resource->"architecture-name" . " " . $Caller . "/Fetch (https://rsc.eworm.de/)");
+}
+
+# format a line for output
+:set FormatLine do={
+ :local Key [ :tostr $1 ];
+ :local Value [ :tostr $2 ];
+ :local Indent [ :tonum $3 ];
+ :local Spaces;
+ :local Return "";
+
+ :global CharacterMultiply;
+ :global EitherOr;
+
+ :set Indent [ $EitherOr $Indent 16 ];
+ :local Spaces [ $CharacterMultiply " " $Indent ];
+
+ :if ([ :len $Key ] > 0) do={ :set Return ($Key . ":"); }
+ :if ([ :len $Key ] > ($Indent - 2)) do={
+ :set Return ($Return . "\n" . [ :pick $Spaces 0 $Indent ] . $Value);
+ } else={
+ :set Return ($Return . [ :pick $Spaces 0 ($Indent - [ :len $Return ]) ] . $Value);
+ }
+
+ :return $Return;
+}
+
+# format multiple lines for output
+:set FormatMultiLines do={
+ :local Key [ :tostr $1 ];
+ :local Values [ :toarray $2 ];
+ :local Indent [ :tonum $3 ];
+ :local Return;
+
+ :global FormatLine;
+
+ :set Return [ $FormatLine $Key ($Values->0) $Indent ];
+ :foreach Value in=[ :pick $Values 1 [ :len $Values ] ] do={
+ :set Return ($Return . "\n" . [ $FormatLine "" $Value $Indent ]);
+ }
+
+ :return $Return;
+}
+
+# get MAC vendor
+:set GetMacVendor do={
+ :local Mac [ :tostr $1 ];
+
+ :global CertificateAvailable;
+ :global IsMacLocallyAdministered;
+ :global LogPrint;
+
+ :if ([ $IsMacLocallyAdministered $Mac ] = true) do={
+ :return "locally administered";
+ }
+
+ :do {
+ :if ([ $CertificateAvailable "GTS CA 1P5" ] = false) do={
+ $LogPrint warning $0 ("Downloading required certificate failed.");
+ :error false;
+ }
+ :local Vendor ([ /tool/fetch check-certificate=yes-without-crl \
+ ("https://api.macvendors.com/" . [ :pick $Mac 0 8 ]) output=user as-value ]->"data");
+ :return $Vendor;
+ } on-error={
+ :do {
+ /tool/fetch check-certificate=yes-without-crl ("https://api.macvendors.com/") \
+ output=none as-value;
+ $LogPrint debug $0 ("The mac vendor is not known in database.");
+ } on-error={
+ $LogPrint warning $0 ("Failed getting mac vendor.");
+ }
+ :return "unknown vendor";
+ }
+}
+
+# generate random 20 chars alphabetical (A-Z & a-z) and numerical (0-9)
+:set GetRandom20CharAlNum do={
+ :global EitherOr;
+
+ :return [ :rndstr length=[ $EitherOr [ :tonum $1 ] 20 ] from="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ];
+}
+
+# generate random 20 chars hex (0-9 and a-f)
+:set GetRandom20CharHex do={
+ :global EitherOr;
+
+ :return [ :rndstr length=[ $EitherOr [ :tonum $1 ] 20 ] from="0123456789abcdef" ];
+}
+
+# generate random number
+:set GetRandomNumber do={
+ :global EitherOr;
+
+ :return [ :rndnum from=0 to=[ $EitherOr [ :tonum $1 ] 4294967295 ] ];
+}
+
+# return first line that matches a pattern
+:set Grep do={
+ :local Input ([ :tostr $1 ] . "\n");
+ :local Pattern [ :tostr $2 ];
+
+ :if ([ :typeof [ :find $Input $Pattern ] ] = "nil") do={
+ :return [];
+ }
+
+ :do {
+ :local Line [ :pick $Input 0 [ :find $Input "\n" ] ];
+ :if ([ :typeof [ :find $Line $Pattern ] ] = "num") do={
+ :return $Line;
+ }
+ :set Input [ :pick $Input ([ :find $Input "\n" ] + 1) [ :len $Input ] ];
+ } while=([ :len $Input ] > 0);
+
+ :return [];
+}
+
+# convert from hex (string) to num
+:set HexToNum do={
+ :local Input [ :tostr $1 ];
+
+ :global HexToNum;
+
+ :if ([ :pick $Input 0 ] = "*") do={
+ :return [ $HexToNum [ :pick $Input 1 [ :len $Input ] ] ];
+ }
+
+ :return [ :tonum ("0x" . $Input) ];
+}
+
+# return human readable number
+:set HumanReadableNum do={
+ :local Input [ :tonum $1 ];
+ :local Base [ :tonum $2 ];
+
+ :global EitherOr;
+
+ :local Prefix "kMGTPE";
+ :local Pow 1;
+
+ :set Base [ $EitherOr $Base 1024 ];
+
+ :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);
+ }
+ :return ($Tmp2 . "." . [ :pick $Tmp1 [ :len $Tmp2 ] ([ :len $Tmp1 ] - [ :len $Tmp2 ] + 1) ] . $Prefix);
+ }
+ }
+}
+
+# mimic conditional/ternary operator (condition ? consequent : alternative)
+:set IfThenElse do={
+ :if ([ :tostr $1 ] = "true" || [ :tobool $1 ] = true) do={
+ :return $2;
+ }
+ :return $3;
+}
+
+# check if default route is reachable
+:set IsDefaultRouteReachable do={
+ :if ([ :len [ /ip/route/find where dst-address=0.0.0.0/0 active routing-table=main ] ] > 0) do={
+ :return true;
+ }
+ :return false;
+}
+
+# check if DNS is resolving
+:set IsDNSResolving do={
+ :do {
+ :resolve "low-ttl.eworm.de";
+ } on-error={
+ :return false;
+ }
+ :return true;
+}
+
+# check if system is is fully connected (default route reachable, DNS resolving, time sync)
+:set IsFullyConnected do={
+ :global IsDefaultRouteReachable;
+ :global IsDNSResolving;
+ :global IsTimeSync;
+
+ :if ([ $IsDefaultRouteReachable ] = false) do={
+ :return false;
+ }
+ :if ([ $IsDNSResolving ] = false) do={
+ :return false;
+ }
+ :if ([ $IsTimeSync ] = false) do={
+ :return false;
+ }
+ :return true;
+}
+
+# check if mac address is locally administered
+:set IsMacLocallyAdministered do={
+ :if ([ :tonum ("0x" . [ :pick $1 0 [ :find $1 ":" ] ]) ] & 2 = 2) do={
+ :return true;
+ }
+ :return false;
+}
+
+# check if system time is sync
+:set IsTimeSync do={
+ :global IsTimeSyncCached;
+ :global IsTimeSyncResetNtp;
+
+ :global LogPrint;
+
+ :if ($IsTimeSyncCached = true) do={
+ :return true;
+ }
+
+ :if ([ /system/ntp/client/get enabled ] = true) do={
+ :if ([ /system/ntp/client/get status ] = "synchronized") do={
+ :set IsTimeSyncCached true;
+ :return true;
+ }
+
+ :if ([ :typeof $IsTimeSyncResetNtp ] = "nothing") do={
+ :set IsTimeSyncResetNtp 0s;
+ }
+ :local Uptime [ /system/resource/get uptime ];
+ :if ($Uptime - $IsTimeSyncResetNtp < 3m) do={
+ :return false;
+ }
+
+ :set IsTimeSyncResetNtp $Uptime;
+ /system/ntp/client/set enabled=no;
+ :delay 20ms;
+ /system/ntp/client/set enabled=yes;
+ :return false;
+ }
+
+ :if ([ /system/license/get ]->"level" = "free" || \
+ [ /system/resource/get ]->"board-name" = "x86") do={
+ $LogPrint debug $0 ("No ntp client configured, relying on RTC for CHR free license and x86.");
+ :return true;
+ }
+
+ :if ([ /ip/cloud/get update-time ] = true) do={
+ :if ([ :typeof [ /ip/cloud/get public-address ] ] = "ip") do={
+ :set IsTimeSyncCached true;
+ :return true;
+ }
+ :return false;
+ }
+
+ $LogPrint debug $0 ("No time source configured! Returning gracefully...");
+ :return true;
+}
+
+# log and print with same text
+:set LogPrint do={
+ :local Severity [ :tostr $1 ];
+ :local Name [ :tostr $2 ];
+ :local Message [ :tostr $3 ];
+
+ :global PrintDebug;
+ :global PrintDebugOverride;
+
+ :global EitherOr;
+
+ :local Debug [ $EitherOr ($PrintDebugOverride->$Name) $PrintDebug ];
+
+ :local PrintSeverity do={
+ :global TerminalColorOutput;
+
+ :if ($TerminalColorOutput != true) do={
+ :return $1;
+ }
+
+ :local Color { debug=96; info=97; warning=93; error=91 };
+ :return ("\1B[" . $Color->$1 . "m" . $1 . "\1B[0m");
+ }
+
+ :local Log ([ $EitherOr $Name "<unknown>" ] . ": " . $Message);
+ :if ($Severity ~ ("^(debug|error|info)\$")) do={
+ :if ($Severity = "debug") do={ :log debug $Log; }
+ :if ($Severity = "error") do={ :log error $Log; }
+ :if ($Severity = "info" ) do={ :log info $Log; }
+ } else={
+ :log warning $Log;
+ :set Severity "warning";
+ }
+
+ :if ($Severity != "debug" || $Debug = true) do={
+ :put ([ $PrintSeverity $Severity ] . ": " . $Message);
+ }
+}
+
+# log and print with same text, optionally exit
+# Deprectated! - TODO: remove later
+:set LogPrintExit2 do={
+ :local Severity [ :tostr $1 ];
+ :local Name [ :tostr $2 ];
+ :local Message [ :tostr $3 ];
+ :local Exit [ :tostr $4 ];
+
+ :global LogPrint;
+ :global LogPrintOnce;
+
+ $LogPrintOnce warning $0 \
+ ("This function is deprecated and will be removed. Please make your adjustments!");
+
+ $LogPrint $1 $2 $3;
+
+ :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;
+}
+
+# get max value
+:set MAX do={
+ :if ($1 > $2) do={ :return $1; }
+ :return $2;
+}
+
+# get min value
+:set MIN do={
+ :if ($1 < $2) do={ :return $1; }
+ :return $2;
+}
+
+# create directory
+:set MkDir do={
+ :local Path [ :tostr $1 ];
+
+ :global CleanFilePath;
+ :global LogPrint;
+ :global WaitForFile;
+
+ :local MkTmpfs do={
+ :global LogPrint;
+ :global WaitForFile;
+
+ :if ([ :len [ /disk/find where slot=tmpfs type=tmpfs ] ] = 1) do={
+ :return true;
+ }
+
+ $LogPrint info $0 ("Creating disk of type tmpfs.");
+ /file/remove [ find where name="tmpfs" type="directory" ];
+ :do {
+ /disk/add slot=tmpfs type=tmpfs tmpfs-max-size=([ /system/resource/get total-memory ] / 3);
+ $WaitForFile "tmpfs";
+ } on-error={
+ $LogPrint warning $0 ("Creating disk of type tmpfs failed!");
+ :return false;
+ }
+ :return true;
+ }
+
+ :set Path [ $CleanFilePath $Path ];
+
+ :if ($Path = "") do={
+ :return true;
+ }
+
+ :if ([ :len [ /file/find where name=$Path type="directory" ] ] = 1) do={
+ :return true;
+ }
+
+ :if ([ :pick $Path 0 5 ] = "tmpfs") do={
+ :if ([ $MkTmpfs ] = false) do={
+ :return false;
+ }
+ }
+
+ :do {
+ :local File ($Path . "/file");
+ /file/add name=$File;
+ $WaitForFile $File;
+ /file/remove $File;
+ } on-error={
+ $LogPrint warning $0 ("Making directory '" . $Path . "' failed!");
+ :return false;
+ }
+
+ :return true;
+}
+
+# prepare NotificationFunctions array
+:if ([ :typeof $NotificationFunctions ] != "array") do={
+ :set NotificationFunctions ({});
+}
+
+# parse the date and return a named array
+:set ParseDate do={
+ :local Date [ :tostr $1 ];
+
+ :return ({ "year"=[ :tonum [ :pick $Date 0 4 ] ];
+ "month"=[ :tonum [ :pick $Date 5 7 ] ];
+ "day"=[ :tonum [ :pick $Date 8 10 ] ] });
+}
+
+# parse JSON into array
+# Warning: This is not a complete parser!
+:set ParseJson do={
+ :local Input [ :tostr $1 ];
+
+ :local InLen;
+ :local Return ({});
+ :local Skip 0;
+
+ :if ([ :pick $Input 0 ] = "{") do={
+ :set Input [ :pick $Input 1 ([ :len $Input ] - 1) ];
+ }
+ :set Input [ :toarray $Input ];
+ :set InLen [ :len $Input ];
+
+ :for I from=0 to=$InLen do={
+ :if ($Skip > 0 || $Input->$I = "\n" || $Input->$I = "\r\n") do={
+ :if ($Skip > 0) do={
+ :set $Skip ($Skip - 1);
+ }
+ } else={
+ :local Done false;
+ :local Key ($Input->$I);
+ :local Val1 ($Input->($I + 1));
+ :local Val2 ($Input->($I + 2));
+ :if ($Val1 = ":") do={
+ :set Skip 2;
+ :set ($Return->$Key) $Val2;
+ :set Done true;
+ }
+ :if ($Done = false && $Val1 = ":[") do={
+ :local Last false;
+ :set Skip 1;
+ :set ($Return->$Key) ({});
+ :do {
+ :set Skip ($Skip + 1);
+ :local ValX ($Input->($I + $Skip));
+ :if ([ :pick $ValX ([ :len $ValX ] - 1) ] = "]") do={
+ :set Last true;
+ :set ValX [ :pick $ValX 0 ([ :len $ValX ] - 1) ];
+ }
+ :set ($Return->$Key) (($Return->$Key), $ValX);
+ } while=($Last = false && $I + $Skip < $InLen);
+ :set Done true;
+ }
+ :if ($Done = false && $Val1 = ":[]") do={
+ :set Skip 1;
+ :set ($Return->$Key) ({});
+ :set Done true;
+ }
+ :if ($Done = false) do={
+ :set Skip 1;
+ :set ($Return->$Key) [ :pick $Val1 1 [ :len $Val1 ] ];
+ }
+ }
+ }
+
+ :return $Return;
+}
+
+# parse key value store
+:set ParseKeyValueStore do={
+ :local Source $1;
+ :if ([ :typeof $Source ] != "array") do={
+ :set Source [ :tostr $1 ];
+ }
+ :local Result ({});
+ :foreach KeyValue in=[ :toarray $Source ] do={
+ :if ([ :find $KeyValue "=" ]) do={
+ :set ($Result->[ :pick $KeyValue 0 [ :find $KeyValue "=" ] ]) \
+ [ :pick $KeyValue ([ :find $KeyValue "=" ] + 1) [ :len $KeyValue ] ];
+ } else={
+ :set ($Result->$KeyValue) true;
+ }
+ }
+ :return $Result;
+}
+
+# print lines with trailing carriage return
+:set PrettyPrint do={
+ :local Input [ :tostr $1 ];
+
+ :global Unix2Dos;
+
+ :put [ $Unix2Dos $Input ];
+}
+
+# delay a random amount of seconds
+:set RandomDelay do={
+ :local Time [ :tonum $1 ];
+ :local Unit [ :tostr $2 ];
+
+ :global EitherOr;
+ :global GetRandomNumber;
+ :global MAX;
+
+ :if ($Time = 0) do={
+ :return false;
+ }
+
+ :delay ([ $MAX 10 [ $GetRandomNumber ([ :tonsec [ :totime ($Time . [ $EitherOr $Unit "s" ]) ] ] / 1000000) ] ] . "ms");
+}
+
+# check for required RouterOS version
+:set RequiredRouterOS do={
+ :local Caller [ :tostr $1 ];
+ :local Required [ :tostr $2 ];
+ :local Warn [ :tostr $3 ];
+
+ :global IfThenElse;
+ :global LogPrint;
+ :global VersionToNum;
+
+ :if (!($Required ~ "^\\d+\\.\\d+((alpha|beta|rc|\\.)\\d+|)\$")) do={
+ $LogPrint error $0 ("No valid RouterOS version: " . $Required);
+ :return false;
+ }
+
+ :if ([ $VersionToNum $Required ] > [ $VersionToNum [ /system/package/update/get installed-version ] ]) do={
+ :if ($Warn = "true") do={
+ $LogPrint warning $0 ("This " . [ $IfThenElse ([ :pick $Caller 0 ] = ("\$")) "function" "script" ] . \
+ " '" . $Caller . "' (at least specific functionality) requires RouterOS " . $Required . ". Please update!");
+ }
+ :return false;
+ }
+ :return true;
+}
+
+# check if script is run from terminal
+:set ScriptFromTerminal do={
+ :local Script [ :tostr $1 ];
+
+ :global LogPrint;
+
+ :foreach Job in=[ /system/script/job/find where script=$Script ] do={
+ :set Job [ /system/script/job/get $Job ];
+ :while ([ :typeof ($Job->"parent") ] = "id") do={
+ :set Job [ /system/script/job/get [ find where .id=($Job->"parent") ] ];
+ }
+ :if (($Job->"type") = "login") do={
+ $LogPrint debug $0 ("Script " . $Script . " started from terminal.");
+ :return true;
+ }
+ }
+ $LogPrint debug $0 ("Script " . $Script . " NOT started from terminal.");
+
+ :return false;
+}
+
+# install new scripts, update existing scripts
+:set ScriptInstallUpdate do={
+ :local Scripts [ :toarray $1 ];
+ :local NewComment [ :tostr $2 ];
+
+ :global ExpectedConfigVersion;
+ :global Identity;
+ :global IDonate;
+ :global NoNewsAndChangesNotification;
+ :global ScriptUpdatesBaseUrl;
+ :global ScriptUpdatesUrlSuffix;
+
+ :global CertificateAvailable;
+ :global EitherOr;
+ :global FetchUserAgent;
+ :global Grep;
+ :global IfThenElse;
+ :global LogPrint;
+ :global LogPrintOnce;
+ :global ParseKeyValueStore;
+ :global RequiredRouterOS;
+ :global SendNotification2;
+ :global SymbolForNotification;
+ :global ValidateSyntax;
+
+ :if ([ $CertificateAvailable "E1" ] = false) do={
+ $LogPrint warning $0 ("Downloading certificate failed, trying without.");
+ }
+
+ :foreach Script in=$Scripts do={
+ :if ([ :len [ /system/script/find where name=$Script ] ] = 0) do={
+ $LogPrint info $0 ("Adding new script: " . $Script);
+ /system/script/add name=$Script owner=$Script source="#!rsc by RouterOS\n" comment=$NewComment;
+ }
+ }
+
+ :local ExpectedConfigVersionBefore $ExpectedConfigVersion;
+ :local ReloadGlobalFunctions false;
+ :local ReloadGlobalConfig false;
+
+ :foreach Script in=[ /system/script/find where source~"^#!rsc by RouterOS\r?\n" ] do={
+ :local ScriptVal [ /system/script/get $Script ];
+ :local ScriptInfo [ $ParseKeyValueStore ($ScriptVal->"comment") ];
+ :local SourceNew;
+
+ :foreach Scheduler in=[ /system/scheduler/find where on-event~("\\b" . $ScriptVal->"name" . "\\b") ] do={
+ :local SchedulerVal [ /system/scheduler/get $Scheduler ];
+ :if ($ScriptVal->"policy" != $SchedulerVal->"policy") do={
+ $LogPrint warning $0 ("Policies differ for script '" . $ScriptVal->"name" . \
+ "' and its scheduler '" . $SchedulerVal->"name" . "'!");
+ }
+ }
+
+ :if (!($ScriptInfo->"ignore" = true)) do={
+ :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=({ [ $FetchUserAgent $0 ] }) $Url output=user as-value ];
+ :if ($Result->"status" = "finished") do={
+ :set SourceNew ($Result->"data");
+ }
+ } on-error={
+ :if ($ScriptVal->"source" = "#!rsc by RouterOS\n") do={
+ $LogPrint warning $0 ("Failed fetching script '" . $ScriptVal->"name" . \
+ "', removing dummy. Typo on installation?");
+ /system/script/remove $Script;
+ } else={
+ $LogPrint warning $0 ("Failed fetching script '" . $ScriptVal->"name" . "'!");
+ }
+ }
+ }
+
+ :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={
+ $LogPrint info $0 ("Updating script: " . $ScriptVal->"name");
+ /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={
+ $LogPrint warning $0 ("Syntax validation for script '" . $ScriptVal->"name" . \
+ "' failed! Ignoring!");
+ }
+ } else={
+ $LogPrintOnce warning $0 ("The script '" . $ScriptVal->"name" . "' requires RouterOS " . \
+ $Required . ", which is not met by your installation. Ignoring!");
+ }
+ } else={
+ $LogPrint warning $0 ("Looks like new script '" . $ScriptVal->"name" . \
+ "' is not valid (missing shebang). Ignoring!");
+ }
+ } else={
+ $LogPrint debug $0 ("Script '" . $ScriptVal->"name" . "' did not change.");
+ }
+ } else={
+ $LogPrint debug $0 ("No update for script '" . $ScriptVal->"name" . "'.");
+ }
+ }
+
+ :if ($ReloadGlobalFunctions = true) do={
+ $LogPrint info $0 ("Reloading global functions.");
+ :do {
+ /system/script/run global-functions;
+ } on-error={
+ $LogPrint error $0 ("Reloading global functions failed!");
+ }
+ }
+
+ :if ($ReloadGlobalConfig = true) do={
+ $LogPrint info $0 ("Reloading global configuration.");
+ :do {
+ /system/script/run global-config;
+ } on-error={
+ $LogPrint error $0 ("Reloading global configuration failed!" . \
+ " Syntax error or missing overlay?");
+ }
+ }
+
+ :if ($ExpectedConfigVersionBefore > $ExpectedConfigVersion) do={
+ $LogPrint warning $0 ("The configuration version decreased from " . \
+ $ExpectedConfigVersionBefore . " to " . $ExpectedConfigVersion . \
+ ". Installed an older version?");
+ }
+
+ :if ($ExpectedConfigVersionBefore < $ExpectedConfigVersion) do={
+ :global GlobalConfigChanges;
+ :global GlobalConfigMigration;
+ :local ChangeLogCode;
+
+ :do {
+ :local Url ($ScriptUpdatesBaseUrl . "news-and-changes.rsc" . $ScriptUpdatesUrlSuffix);
+ $LogPrint debug $0 ("Fetching news, changes and migration: " . $Url);
+ :local Result [ /tool/fetch check-certificate=yes-without-crl \
+ http-header-field=({ [ $FetchUserAgent $0 ] }) $Url output=user as-value ];
+ :if ($Result->"status" = "finished") do={
+ :set ChangeLogCode ($Result->"data");
+ }
+ } on-error={
+ $LogPrint warning $0 ("Failed fetching news, changes and migration!");
+ }
+
+ :if ([ :len $ChangeLogCode ] > 0) do={
+ :if ([ $ValidateSyntax $ChangeLogCode ] = true) do={
+ :do {
+ [ :parse $ChangeLogCode ];
+ } on-error={
+ $LogPrint warning $0 ("The changelog failed to run!");
+ }
+ } else={
+ $LogPrint warning $0 ("The changelog failed syntax validation!");
+ }
+ }
+
+ :if ([ :len $GlobalConfigMigration ] > 0) do={
+ :for I from=($ExpectedConfigVersionBefore + 1) to=$ExpectedConfigVersion do={
+ :local Migration ($GlobalConfigMigration->[ :tostr $I ]);
+ :if ([ :typeof $Migration ] = "str") do={
+ :if ([ $ValidateSyntax $Migration ] = true) do={
+ $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!");
+ }
+ } else={
+ $LogPrint warning $0 ("Migration code for change " . $I . " failed syntax validation!");
+ }
+ }
+ }
+ }
+
+ :local NotificationMessage ("The configuration version on " . $Identity . " increased " . \
+ "to " . $ExpectedConfigVersion . ", current configuration may need modification. " . \
+ "Please review and update global-config-overlay, then re-run global-config.");
+ $LogPrint info $0 ($NotificationMessage);
+
+ :if ([ :len $GlobalConfigChanges ] > 0) do={
+ :set NotificationMessage ($NotificationMessage . "\n\nChanges:");
+ :for I from=($ExpectedConfigVersionBefore + 1) to=$ExpectedConfigVersion do={
+ :local Change ($GlobalConfigChanges->[ :tostr $I ]);
+ :set NotificationMessage ($NotificationMessage . "\n " . \
+ [ $SymbolForNotification "pushpin" "*" ] . $Change);
+ $LogPrint info $0 ("Change " . $I . ": " . $Change);
+ }
+ } else={
+ :set NotificationMessage ($NotificationMessage . "\n\nNews and changes are not available.");
+ }
+
+ :if ($NoNewsAndChangesNotification != true) do={
+ :local Link;
+ :if ($IDonate != true) do={
+ :set NotificationMessage ($NotificationMessage . \
+ "\n\n==== donation hint ====\n" . \
+ "This project is developed in private spare time and usage is " . \
+ "free of charge for you. If you like the scripts and think this is " . \
+ "of value for you or your business please consider a donation.");
+ :set Link "https://rsc.eworm.de/#donate";
+ }
+
+ $SendNotification2 ({ origin=$0; \
+ subject=([ $SymbolForNotification "pushpin" ] . "News and configuration changes"); \
+ message=$NotificationMessage; link=$Link });
+ }
+
+ :set GlobalConfigChanges;
+ :set GlobalConfigMigration;
+ }
+}
+
+# lock script against multiple invocation
+:set ScriptLock do={
+ :local Script [ :tostr $1 ];
+ :local WaitMax ([ :tonum $3 ] * 10);
+
+ :global GetRandom20CharAlNum;
+ :global IfThenElse;
+ :global LogPrint;
+
+ :global ScriptLockOrder;
+ :if ([ :typeof $ScriptLockOrder ] = "nothing") do={
+ :set ScriptLockOrder ({});
+ }
+ :if ([ :typeof ($ScriptLockOrder->$Script) ] = "nothing") do={
+ :set ($ScriptLockOrder->$Script) ({});
+ }
+
+ :local JobCount do={
+ :local Script [ :tostr $1 ];
+
+ :return [ :len [ /system/script/job/find where script=$Script ] ];
+ }
+
+ :local TicketCount do={
+ :local Script [ :tostr $1 ];
+
+ :global ScriptLockOrder;
+
+ :local Count 0;
+ :foreach Ticket in=($ScriptLockOrder->$Script) do={
+ :if ([ :typeof $Ticket ] != "nothing") do={
+ :set Count ($Count + 1);
+ }
+ }
+ :return $Count;
+ }
+
+ :local IsFirstTicket do={
+ :local Script [ :tostr $1 ];
+ :local Check [ :tostr $2 ];
+
+ :global ScriptLockOrder;
+
+ :foreach Ticket in=($ScriptLockOrder->$Script) do={
+ :if ($Ticket = $Check) do={ :return true; }
+ :if ([ :typeof $Ticket ] != "nothing" && $Ticket != $Check) do={ :return false; }
+ }
+ :return false;
+ }
+
+ :local AddTicket do={
+ :local Script [ :tostr $1 ];
+ :local Add [ :tostr $2 ];
+
+ :global ScriptLockOrder;
+
+ :while (true) do={
+ :local Pos [ :len ($ScriptLockOrder->$Script) ];
+ :set ($ScriptLockOrder->$Script->$Pos) $Add;
+ :delay 10ms;
+ :if (($ScriptLockOrder->$Script->$Pos) = $Add) do={ :return true; }
+ }
+ }
+
+ :local RemoveTicket do={
+ :local Script [ :tostr $1 ];
+ :local Remove [ :tostr $2 ];
+
+ :global ScriptLockOrder;
+
+ :foreach Id,Ticket in=($ScriptLockOrder->$Script) do={
+ :while (($ScriptLockOrder->$Script->$Id) = $Remove) do={
+ :set ($ScriptLockOrder->$Script->$Id);
+ :delay 10ms;
+ }
+ }
+ }
+
+ :local CleanupTickets do={
+ :local Script [ :tostr $1 ];
+
+ :global ScriptLockOrder;
+
+ :foreach Ticket in=($ScriptLockOrder->$Script) do={
+ :if ([ :typeof $Ticket ] != "nothing") do={
+ :return false;
+ }
+ }
+
+ :set ($ScriptLockOrder->$Script) ({});
+ }
+
+ :if ([ :len [ /system/script/find where name=$Script ] ] = 0) do={
+ $LogPrint error $0 ("A script named '" . $Script . "' does not exist!");
+ :error false;
+ }
+
+ :if ([ $JobCount $Script ] = 0) do={
+ $LogPrint error $0 ("No script '" . $Script . "' is running!");
+ :error false;
+ }
+
+ :if ([ $TicketCount $Script ] >= [ $JobCount $Script ]) do={
+ $LogPrint error $0 ("More tickets than running scripts '" . $Script . "', resetting!");
+ :set ($ScriptLockOrder->$Script) ({});
+ /system/script/job/remove [ find where script=$Script ];
+ }
+
+ :local MyTicket [ $GetRandom20CharAlNum 6 ];
+ $AddTicket $Script $MyTicket;
+
+ :local WaitCount 0;
+ :while ($WaitMax > $WaitCount && ([ $IsFirstTicket $Script $MyTicket ] = false || [ $TicketCount $Script ] < [ $JobCount $Script ])) do={
+ :set WaitCount ($WaitCount + 1);
+ :delay 100ms;
+ }
+
+ :if ([ $IsFirstTicket $Script $MyTicket ] = true && [ $TicketCount $Script ] = [ $JobCount $Script ]) do={
+ $RemoveTicket $Script $MyTicket;
+ $CleanupTickets $Script;
+ :return true;
+ }
+
+ $RemoveTicket $Script $MyTicket;
+ $LogPrint info $0 ("Script '" . $Script . "' started more than once" . [ $IfThenElse ($WaitCount > 0) \
+ " and timed out waiting for lock" "" ] . "...");
+ :return false;
+}
+
+# send notification via NotificationFunctions - expects at least two string arguments
+:set SendNotification do={
+ :global SendNotification2;
+
+ $SendNotification2 ({ subject=$1; message=$2; link=$3; silent=$4 });
+}
+
+# send notification via NotificationFunctions - expects one array argument
+:set SendNotification2 do={
+ :local Notification $1;
+
+ :global NotificationFunctions;
+
+ :foreach FunctionName,Discard in=$NotificationFunctions do={
+ ($NotificationFunctions->$FunctionName) \
+ ("\$NotificationFunctions->\"" . $FunctionName . "\"") \
+ $Notification;
+ }
+}
+
+# return UTF-8 symbol for unicode name
+:set SymbolByUnicodeName do={
+ :local Name [ :tostr $1 ];
+
+ :global LogPrintOnce;
+
+ :local Symbols {
+ "abacus"="\F0\9F\A7\AE";
+ "alarm-clock"="\E2\8F\B0";
+ "arrow-down"="\E2\AC\87";
+ "arrow-up"="\E2\AC\86";
+ "calendar"="\F0\9F\93\85";
+ "card-file-box"="\F0\9F\97\83";
+ "chart-decreasing"="\F0\9F\93\89";
+ "chart-increasing"="\F0\9F\93\88";
+ "cloud"="\E2\98\81";
+ "cross-mark"="\E2\9D\8C";
+ "earth"="\F0\9F\8C\8D";
+ "fire"="\F0\9F\94\A5";
+ "floppy-disk"="\F0\9F\92\BE";
+ "gear"="\E2\9A\99";
+ "heart"="\E2\99\A5";
+ "high-voltage-sign"="\E2\9A\A1";
+ "incoming-envelope"="\F0\9F\93\A8";
+ "information"="\E2\84\B9";
+ "large-orange-circle"="\F0\9F\9F\A0";
+ "large-red-circle"="\F0\9F\94\B4";
+ "link"="\F0\9F\94\97";
+ "lock-with-ink-pen"="\F0\9F\94\8F";
+ "memo"="\F0\9F\93\9D";
+ "mobile-phone"="\F0\9F\93\B1";
+ "pushpin"="\F0\9F\93\8C";
+ "scissors"="\E2\9C\82";
+ "smiley-partying-face"="\F0\9F\A5\B3";
+ "smiley-smiling-face"="\E2\98\BA";
+ "smiley-winking-face-with-tongue"="\F0\9F\98\9C";
+ "sparkles"="\E2\9C\A8";
+ "speech-balloon"="\F0\9F\92\AC";
+ "star"="\E2\AD\90";
+ "warning-sign"="\E2\9A\A0";
+ "white-heavy-check-mark"="\E2\9C\85"
+ }
+
+ :if ([ :len ($Symbols->$Name) ] = 0) do={
+ $LogPrintOnce warning $0 ("No symbol available for name '" . $Name . "'!");
+ :return "";
+ }
+
+ :return (($Symbols->$Name) . "\EF\B8\8F");
+}
+
+# return symbol for notification
+:set SymbolForNotification do={
+ :global NotificationsWithSymbols;
+ :global SymbolByUnicodeName;
+ :global IfThenElse;
+
+ :if ($NotificationsWithSymbols != true) do={
+ :return [ $IfThenElse ([ :len $2 ] > 0) ([ :tostr $2 ] . " ") "" ];
+ }
+ :local Return "";
+ :foreach Symbol in=[ :toarray $1 ] do={
+ :set Return ($Return . [ $SymbolByUnicodeName $Symbol ]);
+ }
+ :return ($Return . " ");
+}
+
+# convert line endings, UNIX -> DOS
+:set Unix2Dos do={
+ :local Input [ :tostr $1 ];
+
+ :global CharacterReplace;
+
+ :return [ $CharacterReplace [ $CharacterReplace $Input \
+ ("\n") ("\r\n") ] ("\r\r\n") ("\r\n") ];
+}
+
+# url encoding
+:set UrlEncode do={
+ :local Input [ :tostr $1 ];
+
+ :if ([ :len $Input ] = 0) do={
+ :return "";
+ }
+
+ :local Return "";
+ :local Chars ("\n\r !\"#\$%&'()*+,:;<=>?@[\\]^`{|}~");
+ :local Subs { "%0A"; "%0D"; "%20"; "%21"; "%22"; "%23"; "%24"; "%25"; "%26"; "%27";
+ "%28"; "%29"; "%2A"; "%2B"; "%2C"; "%3A"; "%3B"; "%3C"; "%3D"; "%3E"; "%3F";
+ "%40"; "%5B"; "%5C"; "%5D"; "%5E"; "%60"; "%7B"; "%7C"; "%7D"; "%7E" };
+
+ :for I from=0 to=([ :len $Input ] - 1) do={
+ :local Char [ :pick $Input $I ];
+ :local Replace [ :find $Chars $Char ];
+
+ :if ([ :typeof $Replace ] = "num") do={
+ :set Char ($Subs->$Replace);
+ }
+ :set Return ($Return . $Char);
+ }
+
+ :return $Return;
+}
+
+# basic syntax validation
+:set ValidateSyntax do={
+ :local Code [ :tostr $1 ];
+
+ :do {
+ [ :parse (":local Validate do={\n" . $Code . "\n}") ];
+ } on-error={
+ :return false;
+ }
+ :return true;
+}
+
+# convert version string to numeric value
+:set VersionToNum do={
+ :local Input [ :tostr $1 ];
+ :local Multi 0x1000000;
+ :local Return 0;
+
+ :global CharacterReplace;
+
+ :set Input [ $CharacterReplace $Input "." "," ];
+ :foreach I in={ "alpha"; "beta"; "rc" } do={
+ :set Input [ $CharacterReplace $Input $I ("," . $I . ",") ];
+ }
+
+ :foreach Value in=([ :toarray $Input ], 0) do={
+ :local Num [ :tonum $Value ];
+ :if ($Multi = 0x100) do={
+ :if ([ :typeof $Num ] = "num") do={
+ :set Return ($Return + 0xff00);
+ :set Multi ($Multi / 0x100);
+ } else={
+ :if ($Value = "alpha") do={ :set Return ($Return + 0x3f00); }
+ :if ($Value = "beta") do={ :set Return ($Return + 0x5f00); }
+ :if ($Value = "rc") do={ :set Return ($Return + 0x7f00); }
+ }
+ }
+ :if ([ :typeof $Num ] = "num") do={ :set Return ($Return + ($Value * $Multi)); }
+ :set Multi ($Multi / 0x100);
+ }
+
+ :return $Return;
+}
+
+# wait for default route to be reachable
+:set WaitDefaultRouteReachable do={
+ :global IsDefaultRouteReachable;
+
+ :while ([ $IsDefaultRouteReachable ] = false) do={
+ :delay 1s;
+ }
+}
+
+# wait for DNS to resolve
+:set WaitDNSResolving do={
+ :global IsDNSResolving;
+
+ :while ([ $IsDNSResolving ] = false) do={
+ :delay 1s;
+ }
+}
+
+# wait for file to be available
+:set WaitForFile do={
+ :local FileName [ :tostr $1 ];
+ :local WaitTime [ :totime $2 ];
+
+ :global CleanFilePath;
+ :global EitherOr;
+
+ :set FileName [ $CleanFilePath $FileName ];
+ :local I 1;
+ :local Delay ([ :totime [ $EitherOr $WaitTime 2s ] ] / 20);
+
+ :while ([ :len [ /file/find where name=$FileName ] ] = 0) do={
+ :if ($I >= 20) do={
+ :return false;
+ }
+ :delay $Delay;
+ :set I ($I + 1);
+ }
+ :return true;
+}
+
+# wait to be fully connected (default route is reachable, time is sync, DNS resolves)
+:set WaitFullyConnected do={
+ :global WaitDefaultRouteReachable;
+ :global WaitDNSResolving;
+ :global WaitTimeSync;
+
+ $WaitDefaultRouteReachable;
+ $WaitTimeSync;
+ $WaitDNSResolving;
+}
+
+# wait for time to become synced
+:set WaitTimeSync do={
+ :global IsTimeSync;
+
+ :while ([ $IsTimeSync ] = false) do={
+ :delay 1s;
+ }
+}
+
+# load modules
+:foreach Script in=[ /system/script/find where name ~ "^mod/." ] do={
+ :local ScriptVal [ /system/script/get $Script ];
+ :if ([ $ValidateSyntax ($ScriptVal->"source") ] = true) do={
+ :do {
+ /system/script/run $Script;
+ } on-error={
+ $LogPrint error $0 ("Module '" . $ScriptVal->"name" . "' failed to run.");
+ }
+ } else={
+ $LogPrint error $0 ("Module '" . $ScriptVal->"name" . "' failed syntax validation, skipping.");
+ }
+}
+
+# Log success
+:local Resource [ /system/resource/get ];
+$LogPrintOnce info $ScriptName ("Loaded on " . $Resource->"board-name" . \
+ " with RouterOS " . $Resource->"version" . ".");
+
+# signal we are ready
+:set GlobalFunctionsReady true;
diff --git a/global-wait b/global-wait
deleted file mode 100644
index 3ce6d27..0000000
--- a/global-wait
+++ /dev/null
@@ -1,11 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: global-wait
-
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={
- :delay 500ms;
-}
-
-:global LogPrintExit2;
-
-$LogPrintExit2 warning "global-wait" ("This script is now useless, please remove it from configuration.") true;
diff --git a/global-wait.rsc b/global-wait.rsc
new file mode 100644
index 0000000..f8c767b
--- /dev/null
+++ b/global-wait.rsc
@@ -0,0 +1,12 @@
+#!rsc by RouterOS
+# RouterOS script: global-wait
+# Copyright (c) 2020-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# wait for global-functions to finish
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/global-wait.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
diff --git a/gps-track b/gps-track
deleted file mode 100644
index 3fc3807..0000000
--- a/gps-track
+++ /dev/null
@@ -1,34 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: gps-track
-# Copyright (c) 2018-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# track gps data by sending json data to http server
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/gps-track.md
-
-:local 0 "gps-track";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global GpsTrackUrl;
-:global Identity;
-
-:global LogPrintExit2;
-
-:local CoordinateFormat [ / system gps get coordinate-format ];
-:local Gps [ / system gps monitor once as-value ];
-
-:if ($Gps->"valid" = true) do={
- / tool fetch check-certificate=yes-without-crl $GpsTrackUrl output=none \
- http-method=post http-header-field="Content-Type: application/json" \
- http-data=("{" . \
- "\"lat\":\"" . ($Gps->"latitude") . "\"," . \
- "\"lon\":\"" . ($Gps->"longitude") . "\"," . \
- "\"identity\":\"" . $Identity . "\"" . \
- "}") as-value;
- $LogPrintExit2 debug $0 ("Sending GPS data in " . $CoordinateFormat . " format: " . \
- "lat: " . ($Gps->"latitude") . " " . \
- "lon: " . ($Gps->"longitude")) false;
-} else={
- $LogPrintExit2 debug $0 ("GPS data not valid.") false;
-}
diff --git a/gps-track.rsc b/gps-track.rsc
new file mode 100644
index 0000000..1aeab84
--- /dev/null
+++ b/gps-track.rsc
@@ -0,0 +1,50 @@
+#!rsc by RouterOS
+# RouterOS script: gps-track
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# track gps data by sending json data to http server
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/gps-track.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global GpsTrackUrl;
+ :global Identity;
+
+ :global LogPrint;
+ :global ScriptLock;
+ :global WaitFullyConnected;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+ $WaitFullyConnected;
+
+ :local CoordinateFormat [ /system/gps/get coordinate-format ];
+ :local Gps [ /system/gps/monitor once as-value ];
+
+ :if ($Gps->"valid" = true) do={
+ :do {
+ /tool/fetch check-certificate=yes-without-crl $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;
+ $LogPrint debug $ScriptName ("Sending GPS data in " . $CoordinateFormat . " format: " . \
+ "lat: " . ($Gps->"latitude") . " " . \
+ "lon: " . ($Gps->"longitude"));
+ } on-error={
+ $LogPrint warning $ScriptName ("Failed sending GPS data!");
+ }
+ } else={
+ $LogPrint debug $ScriptName ("GPS data not valid.");
+ }
+} on-error={ }
diff --git a/hotspot-to-wpa b/hotspot-to-wpa
deleted file mode 100644
index 68f887f..0000000
--- a/hotspot-to-wpa
+++ /dev/null
@@ -1,40 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: hotspot-to-wpa
-# Copyright (c) 2019-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# add private WPA passphrase after hotspot login
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/hotspot-to-wpa.md
-
-:local 0 "hotspot-to-wpa";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global LogPrintExit2;
-
-:local MacAddress $"mac-address";
-:local UserName $username;
-:local Date [ / system clock get date ];
-:local PassWord [ / ip hotspot user get [ find where name=$UserName ] password ];
-
-: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);
-
-$LogPrintExit2 info $0 ("Adding/updating accesslist entry for mac address " . $MacAddress . \
- " (user " . $UserName . ").") false;
-
-/ caps-man access-list remove [ find where mac-address=$MacAddress comment~"^hotspot-to-wpa: " ];
-
-:local Limits [ / caps-man access-list get $PlaceBefore ];
-:if (($Limits->"ap-tx-limit") > 0 && ($Limits->"client-tx-limit") > 0) do={
- / caps-man access-list add comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) \
- mac-address=$MacAddress private-passphrase=$PassWord ssid-regexp="-wpa\$" \
- ap-tx-limit=($Limits->"ap-tx-limit") client-tx-limit=($Limits->"client-tx-limit") \
- place-before=$PlaceBefore;
-} else={
- / caps-man access-list add comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) \
- mac-address=$MacAddress private-passphrase=$PassWord ssid-regexp="-wpa\$" place-before=$PlaceBefore;
-}
diff --git a/hotspot-to-wpa-cleanup.capsman.rsc b/hotspot-to-wpa-cleanup.capsman.rsc
new file mode 100644
index 0000000..0540ad5
--- /dev/null
+++ b/hotspot-to-wpa-cleanup.capsman.rsc
@@ -0,0 +1,74 @@
+#!rsc by RouterOS
+# RouterOS script: hotspot-to-wpa-cleanup.capsman
+# Copyright (c) 2021-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: lease-script, order=80
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global EitherOr;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName 10 ] = false) do={
+ :error false;
+ }
+
+ :local DHCPServers ({});
+ :foreach Server in=[ /ip/dhcp-server/find where comment~"hotspot-to-wpa" ] do={
+ :local ServerVal [ /ip/dhcp-server/get $Server ]
+ :local ServerInfo [ $ParseKeyValueStore ($ServerVal->"comment") ];
+ :if (($ServerInfo->"hotspot-to-wpa") = "wpa") do={
+ :set ($DHCPServers->($ServerVal->"name")) \
+ [ :totime [ $EitherOr ($ServerInfo->"timeout") 4w ] ];
+ }
+ }
+
+ :foreach Client in=[ /caps-man/registration-table/find where comment~"^hotspot-to-wpa:" ] do={
+ :local ClientVal [ /caps-man/registration-table/get $Client ];
+ :foreach Lease in=[ /ip/dhcp-server/lease/find where dynamic \
+ mac-address=($ClientVal->"mac-address") ] do={
+ :if (($DHCPServers->[ /ip/dhcp-server/lease/get $Lease server ]) > 0s) do={
+ $LogPrint info $ScriptName ("Client with mac address " . ($ClientVal->"mac-address") . \
+ " connected to WPA, making lease static.");
+ /ip/dhcp-server/lease/make-static $Lease;
+ /ip/dhcp-server/lease/set comment=($ClientVal->"comment") $Lease;
+ }
+ }
+ }
+
+ :foreach Client in=[ /caps-man/access-list/find where comment~"^hotspot-to-wpa:" \
+ !(comment~[ /system/clock/get date ]) ] 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={
+ :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 ];
+ $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={ }
diff --git a/hotspot-to-wpa-cleanup.template.rsc b/hotspot-to-wpa-cleanup.template.rsc
new file mode 100644
index 0000000..6f3b3e1
--- /dev/null
+++ b/hotspot-to-wpa-cleanup.template.rsc
@@ -0,0 +1,81 @@
+#!rsc by RouterOS
+# RouterOS script: hotspot-to-wpa-cleanup%TEMPL%
+# Copyright (c) 2021-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: lease-script, order=80
+# requires RouterOS, version=7.12
+#
+# manage and clean up private WPA passphrase after hotspot login
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/hotspot-to-wpa.md
+#
+# !! This is just a template to generate the real script!
+# !! Pattern '%TEMPL%' is replaced, paths are filtered.
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global EitherOr;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName 10 ] = false) do={
+ :error false;
+ }
+
+ :local DHCPServers ({});
+ :foreach Server in=[ /ip/dhcp-server/find where comment~"hotspot-to-wpa" ] do={
+ :local ServerVal [ /ip/dhcp-server/get $Server ]
+ :local ServerInfo [ $ParseKeyValueStore ($ServerVal->"comment") ];
+ :if (($ServerInfo->"hotspot-to-wpa") = "wpa") do={
+ :set ($DHCPServers->($ServerVal->"name")) \
+ [ :totime [ $EitherOr ($ServerInfo->"timeout") 4w ] ];
+ }
+ }
+
+ :foreach Client in=[ /caps-man/registration-table/find where comment~"^hotspot-to-wpa:" ] do={
+ :foreach Client in=[ /interface/wifi/registration-table/find where comment~"^hotspot-to-wpa:" ] do={
+ :local ClientVal [ /caps-man/registration-table/get $Client ];
+ :local ClientVal [ /interface/wifi/registration-table/get $Client ];
+ :foreach Lease in=[ /ip/dhcp-server/lease/find where dynamic \
+ mac-address=($ClientVal->"mac-address") ] do={
+ :if (($DHCPServers->[ /ip/dhcp-server/lease/get $Lease server ]) > 0s) do={
+ $LogPrint info $ScriptName ("Client with mac address " . ($ClientVal->"mac-address") . \
+ " connected to WPA, making lease static.");
+ /ip/dhcp-server/lease/make-static $Lease;
+ /ip/dhcp-server/lease/set comment=($ClientVal->"comment") $Lease;
+ }
+ }
+ }
+
+ :foreach Client in=[ /caps-man/access-list/find where comment~"^hotspot-to-wpa:" \
+ :foreach Client in=[ /interface/wifi/access-list/find where comment~"^hotspot-to-wpa:" \
+ !(comment~[ /system/clock/get date ]) ] 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={
+ :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 ];
+ $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={ }
diff --git a/hotspot-to-wpa-cleanup.wifi.rsc b/hotspot-to-wpa-cleanup.wifi.rsc
new file mode 100644
index 0000000..9c79628
--- /dev/null
+++ b/hotspot-to-wpa-cleanup.wifi.rsc
@@ -0,0 +1,74 @@
+#!rsc by RouterOS
+# RouterOS script: hotspot-to-wpa-cleanup.wifi
+# Copyright (c) 2021-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: lease-script, order=80
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global EitherOr;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName 10 ] = false) do={
+ :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 ]) ] 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={
+ :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 ];
+ $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={ }
diff --git a/hotspot-to-wpa.capsman.rsc b/hotspot-to-wpa.capsman.rsc
new file mode 100644
index 0000000..37c8464
--- /dev/null
+++ b/hotspot-to-wpa.capsman.rsc
@@ -0,0 +1,98 @@
+#!rsc by RouterOS
+# RouterOS script: hotspot-to-wpa.capsman
+# Copyright (c) 2019-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global EitherOr;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global ScriptLock;
+
+ :local MacAddress $"mac-address";
+ :local UserName $username;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :if ([ :typeof $MacAddress ] = "nothing" || [ :typeof $UserName ] = "nothing") do={
+ $LogPrint error $ScriptName ("This script is supposed to run from hotspot on login.");
+ :error false;
+ }
+
+ :local Date [ /system/clock/get date ];
+ :local UserVal ({});
+ :if ([ :len [ /ip/hotspot/user/find where name=$UserName ] ] > 0) do={
+ :set UserVal [ /ip/hotspot/user/get [ find where name=$UserName ] ];
+ }
+ :local UserInfo [ $ParseKeyValueStore ($UserVal->"comment") ];
+ :local Hotspot [ /ip/hotspot/host/get [ find where mac-address=$MacAddress authorized ] server ];
+
+ :if ([ :len [ /caps-man/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ] ] = 0) do={
+ /caps-man/access-list/add comment="--- hotspot-to-wpa above ---" disabled=yes;
+ $LogPrint warning $ScriptName ("Added disabled access-list entry with comment '--- hotspot-to-wpa above ---'.");
+ }
+ :local PlaceBefore ([ /caps-man/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ]->0);
+
+ :if ([ :len [ /caps-man/access-list/find where \
+ comment=("hotspot-to-wpa template " . $Hotspot) disabled ] ] = 0) do={
+ /caps-man/access-list/add comment=("hotspot-to-wpa template " . $Hotspot) disabled=yes place-before=$PlaceBefore;
+ $LogPrint warning $ScriptName ("Added template in access-list for hotspot '" . $Hotspot . "'.");
+ }
+ :local Template [ /caps-man/access-list/get ([ find where \
+ comment=("hotspot-to-wpa template " . $Hotspot) disabled ]->0) ];
+
+ :if ($Template->"action" = "reject") do={
+ $LogPrint info $ScriptName ("Ignoring login for hotspot '" . $Hotspot . "'.");
+ :error true;
+ }
+
+ # allow login page to load
+ :delay 1s;
+
+ $LogPrint info $ScriptName ("Adding/updating access-list entry for mac address " . $MacAddress . \
+ " (user " . $UserName . ").");
+ /caps-man/access-list/remove [ find where mac-address=$MacAddress comment~"^hotspot-to-wpa: " ];
+ /caps-man/access-list/add private-passphrase=($UserVal->"password") ssid-regexp="-wpa\$" \
+ mac-address=$MacAddress comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) \
+ action=reject place-before=$PlaceBefore;
+
+ :local Entry [ /caps-man/access-list/find where mac-address=$MacAddress \
+ comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) ];
+ :local PrivatePassphrase [ $EitherOr ($UserInfo->"private-passphrase") ($Template->"private-passphrase") ];
+ :if ([ :len $PrivatePassphrase ] > 0) do={
+ :if ($PrivatePassphrase = "ignore") do={
+ /caps-man/access-list/set $Entry !private-passphrase;
+ } else={
+ /caps-man/access-list/set $Entry private-passphrase=$PrivatePassphrase;
+ }
+ }
+ :local SsidRegexp [ $EitherOr ($UserInfo->"ssid-regexp") ($Template->"ssid-regexp") ];
+ :if ([ :len $SsidRegexp ] > 0) do={
+ /caps-man/access-list/set $Entry ssid-regexp=$SsidRegexp;
+ }
+ :local VlanId [ $EitherOr ($UserInfo->"vlan-id") ($Template->"vlan-id") ];
+ :if ([ :len $VlanId ] > 0) do={
+ /caps-man/access-list/set $Entry vlan-id=$VlanId;
+ }
+ :local VlanMode [ $EitherOr ($UserInfo->"vlan-mode") ($Template->"vlan-mode") ];
+ :if ([ :len $VlanMode] > 0) do={
+ /caps-man/access-list/set $Entry vlan-mode=$VlanMode;
+ }
+
+ :delay 2s;
+ /caps-man/access-list/set $Entry action=accept;
+} on-error={ }
diff --git a/hotspot-to-wpa.template.rsc b/hotspot-to-wpa.template.rsc
new file mode 100644
index 0000000..cbce42a
--- /dev/null
+++ b/hotspot-to-wpa.template.rsc
@@ -0,0 +1,118 @@
+#!rsc by RouterOS
+# RouterOS script: hotspot-to-wpa%TEMPL%
+# Copyright (c) 2019-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# add private WPA passphrase after hotspot login
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/hotspot-to-wpa.md
+#
+# !! This is just a template to generate the real script!
+# !! Pattern '%TEMPL%' is replaced, paths are filtered.
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global EitherOr;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global ScriptLock;
+
+ :local MacAddress $"mac-address";
+ :local UserName $username;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :if ([ :typeof $MacAddress ] = "nothing" || [ :typeof $UserName ] = "nothing") do={
+ $LogPrint error $ScriptName ("This script is supposed to run from hotspot on login.");
+ :error false;
+ }
+
+ :local Date [ /system/clock/get date ];
+ :local UserVal ({});
+ :if ([ :len [ /ip/hotspot/user/find where name=$UserName ] ] > 0) do={
+ :set UserVal [ /ip/hotspot/user/get [ find where name=$UserName ] ];
+ }
+ :local UserInfo [ $ParseKeyValueStore ($UserVal->"comment") ];
+ :local Hotspot [ /ip/hotspot/host/get [ find where mac-address=$MacAddress authorized ] server ];
+
+ :if ([ :len [ /caps-man/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ] ] = 0) do={
+ :if ([ :len [ /interface/wifi/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ] ] = 0) do={
+ /caps-man/access-list/add comment="--- hotspot-to-wpa above ---" disabled=yes;
+ /interface/wifi/access-list/add comment="--- hotspot-to-wpa above ---" disabled=yes;
+ $LogPrint warning $ScriptName ("Added disabled access-list entry with comment '--- hotspot-to-wpa above ---'.");
+ }
+ :local PlaceBefore ([ /caps-man/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ]->0);
+ :local PlaceBefore ([ /interface/wifi/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ]->0);
+
+ :if ([ :len [ /caps-man/access-list/find where \
+ :if ([ :len [ /interface/wifi/access-list/find where \
+ comment=("hotspot-to-wpa template " . $Hotspot) disabled ] ] = 0) do={
+ /caps-man/access-list/add comment=("hotspot-to-wpa template " . $Hotspot) disabled=yes place-before=$PlaceBefore;
+ /interface/wifi/access-list/add comment=("hotspot-to-wpa template " . $Hotspot) disabled=yes place-before=$PlaceBefore;
+ $LogPrint warning $ScriptName ("Added template in access-list for hotspot '" . $Hotspot . "'.");
+ }
+ :local Template [ /caps-man/access-list/get ([ find where \
+ :local Template [ /interface/wifi/access-list/get ([ find where \
+ comment=("hotspot-to-wpa template " . $Hotspot) disabled ]->0) ];
+
+ :if ($Template->"action" = "reject") do={
+ $LogPrint info $ScriptName ("Ignoring login for hotspot '" . $Hotspot . "'.");
+ :error true;
+ }
+
+ # allow login page to load
+ :delay 1s;
+
+ $LogPrint info $ScriptName ("Adding/updating access-list entry for mac address " . $MacAddress . \
+ " (user " . $UserName . ").");
+ /caps-man/access-list/remove [ find where mac-address=$MacAddress comment~"^hotspot-to-wpa: " ];
+ /interface/wifi/access-list/remove [ find where mac-address=$MacAddress comment~"^hotspot-to-wpa: " ];
+ /caps-man/access-list/add private-passphrase=($UserVal->"password") ssid-regexp="-wpa\$" \
+ /interface/wifi/access-list/add passphrase=($UserVal->"password") ssid-regexp="-wpa\$" \
+ mac-address=$MacAddress comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) \
+ action=reject place-before=$PlaceBefore;
+
+ :local Entry [ /caps-man/access-list/find where mac-address=$MacAddress \
+ :local Entry [ /interface/wifi/access-list/find where mac-address=$MacAddress \
+ comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) ];
+# NOT /caps-man/ #
+ :set ($Template->"private-passphrase") ($Template->"passphrase");
+# NOT /caps-man/ #
+ :local PrivatePassphrase [ $EitherOr ($UserInfo->"private-passphrase") ($Template->"private-passphrase") ];
+ :if ([ :len $PrivatePassphrase ] > 0) do={
+ :if ($PrivatePassphrase = "ignore") do={
+ /caps-man/access-list/set $Entry !private-passphrase;
+ /interface/wifi/access-list/set $Entry !passphrase;
+ } else={
+ /caps-man/access-list/set $Entry private-passphrase=$PrivatePassphrase;
+ /interface/wifi/access-list/set $Entry passphrase=$PrivatePassphrase;
+ }
+ }
+ :local SsidRegexp [ $EitherOr ($UserInfo->"ssid-regexp") ($Template->"ssid-regexp") ];
+ :if ([ :len $SsidRegexp ] > 0) do={
+ /caps-man/access-list/set $Entry ssid-regexp=$SsidRegexp;
+ /interface/wifi/access-list/set $Entry ssid-regexp=$SsidRegexp;
+ }
+ :local VlanId [ $EitherOr ($UserInfo->"vlan-id") ($Template->"vlan-id") ];
+ :if ([ :len $VlanId ] > 0) do={
+ /caps-man/access-list/set $Entry vlan-id=$VlanId;
+ /interface/wifi/access-list/set $Entry vlan-id=$VlanId;
+ }
+# NOT /interface/wifi/ #
+ :local VlanMode [ $EitherOr ($UserInfo->"vlan-mode") ($Template->"vlan-mode") ];
+ :if ([ :len $VlanMode] > 0) do={
+ /caps-man/access-list/set $Entry vlan-mode=$VlanMode;
+ }
+# NOT /interface/wifi/ #
+
+ :delay 2s;
+ /caps-man/access-list/set $Entry action=accept;
+ /interface/wifi/access-list/set $Entry action=accept;
+} on-error={ }
diff --git a/hotspot-to-wpa.wifi.rsc b/hotspot-to-wpa.wifi.rsc
new file mode 100644
index 0000000..86aeed7
--- /dev/null
+++ b/hotspot-to-wpa.wifi.rsc
@@ -0,0 +1,95 @@
+#!rsc by RouterOS
+# RouterOS script: hotspot-to-wpa.wifi
+# Copyright (c) 2019-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# 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!
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global EitherOr;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global ScriptLock;
+
+ :local MacAddress $"mac-address";
+ :local UserName $username;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :if ([ :typeof $MacAddress ] = "nothing" || [ :typeof $UserName ] = "nothing") do={
+ $LogPrint error $ScriptName ("This script is supposed to run from hotspot on login.");
+ :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 . "'.");
+ :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={ }
diff --git a/ip-addr-bridge b/ip-addr-bridge
deleted file mode 100644
index 762c74e..0000000
--- a/ip-addr-bridge
+++ /dev/null
@@ -1,18 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: ip-addr-bridge
-# Copyright (c) 2018-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# enable or disable ip addresses based on bridge port state
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/ip-addr-bridge.md
-
-:foreach Bridge in=[ / interface bridge find ] do={
- :local BrName [ / interface bridge get $Bridge name ];
- :if ([ :len [ / interface bridge port find where bridge=$BrName ] ] > 0) do={
- :if ([ :len [ / interface bridge port find where bridge=$BrName and inactive=no ] ] = 0) do={
- / ip address disable [ find where !dynamic interface=$BrName ];
- } else={
- / ip address enable [ find where !dynamic interface=$BrName ];
- }
- }
-}
diff --git a/ip-addr-bridge.rsc b/ip-addr-bridge.rsc
new file mode 100644
index 0000000..758cd46
--- /dev/null
+++ b/ip-addr-bridge.rsc
@@ -0,0 +1,18 @@
+#!rsc by RouterOS
+# RouterOS script: ip-addr-bridge
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# enable or disable ip addresses based on bridge port state
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/ip-addr-bridge.md
+
+:foreach Bridge in=[ /interface/bridge/find ] do={
+ :local BrName [ /interface/bridge/get $Bridge name ];
+ :if ([ :len [ /interface/bridge/port/find where bridge=$BrName ] ] > 0) do={
+ :if ([ :len [ /interface/bridge/port/find where bridge=$BrName and inactive=no ] ] = 0) do={
+ /ip/address/disable [ find where !dynamic interface=$BrName ];
+ } else={
+ /ip/address/enable [ find where !dynamic interface=$BrName ];
+ }
+ }
+}
diff --git a/ipsec-to-dns.rsc b/ipsec-to-dns.rsc
new file mode 100644
index 0000000..dd40ca2
--- /dev/null
+++ b/ipsec-to-dns.rsc
@@ -0,0 +1,79 @@
+#!rsc by RouterOS
+# RouterOS script: ipsec-to-dns
+# Copyright (c) 2021-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# and add/remove/update DNS entries from IPSec mode-config
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/ipsec-to-dns.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global Domain;
+ :global HostNameInZone;
+ :global Identity;
+ :global PrefixInZone;
+
+ :global CharacterReplace;
+ :global EscapeForRegEx;
+ :global IfThenElse;
+ :global LogPrint;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :local Zone \
+ ([ $IfThenElse ($PrefixInZone = true) "ipsec." ] . \
+ [ $IfThenElse ($HostNameInZone = true) ($Identity . ".") ] . $Domain);
+ :local Ttl 5m;
+ :local CommentPrefix ("managed by " . $ScriptName . " for ");
+ :local CommentString ("--- " . $ScriptName . " above ---");
+
+ :if ([ :len [ /ip/dns/static/find where (name=$CommentString or (comment=$CommentString and name=-)) type=NXDOMAIN disabled ] ] = 0) do={
+ /ip/dns/static/add name=$CommentString type=NXDOMAIN disabled=yes;
+ $LogPrint warning $ScriptName ("Added disabled static dns record with name '" . $CommentString . "'.");
+ }
+ :local PlaceBefore ([ /ip/dns/static/find where (name=$CommentString or (comment=$CommentString and name=-)) type=NXDOMAIN disabled ]->0);
+
+ :foreach DnsRecord in=[ /ip/dns/static/find where comment~("^" . $CommentPrefix) ] do={
+ :local DnsRecordVal [ /ip/dns/static/get $DnsRecord ];
+ :local PeerId [ $CharacterReplace ($DnsRecordVal->"comment") $CommentPrefix "" ];
+ :if ([ :len [ /ip/ipsec/active-peers/find where id~("^(CN=)?" . [ $EscapeForRegEx $PeerId ] . "\$") \
+ dynamic-address=($DnsRecordVal->"address") ] ] > 0) do={
+ $LogPrint debug $ScriptName ("Peer " . $PeerId . " (" . $DnsRecordVal->"name" . ") still exists. Not deleting DNS entry.");
+ } else={
+ :local Found false;
+ $LogPrint info $ScriptName ("Peer " . $PeerId . " (" . $DnsRecordVal->"name" . ") has gone, deleting DNS entry.");
+ /ip/dns/static/remove $DnsRecord;
+ }
+ }
+
+ :foreach Peer in=[ /ip/ipsec/active-peers/find where !(dynamic-address=[]) ] do={
+ :local PeerVal [ /ip/ipsec/active-peers/get $Peer ];
+ :local PeerId [ $CharacterReplace ($PeerVal->"id") "CN=" "" ];
+ :local Comment ($CommentPrefix . $PeerId);
+ :local HostName [ :pick $PeerId 0 [ :find ($PeerId . ".") "." ] ];
+
+ :local Fqdn ($HostName . "." . $Zone);
+ :local DnsRecord [ /ip/dns/static/find where name=$Fqdn ];
+ :if ([ :len $DnsRecord ] > 0) do={
+ :local DnsIp [ /ip/dns/static/get $DnsRecord address ];
+ :if ($DnsIp = $PeerVal->"dynamic-address") do={
+ $LogPrint debug $ScriptName ("DNS entry for " . $Fqdn . " does not need updating.");
+ } else={
+ $LogPrint info $ScriptName ("Replacing DNS entry for " . $Fqdn . ", new address is " . $PeerVal->"dynamic-address" . ".");
+ /ip/dns/static/set name=$Fqdn address=($PeerVal->"dynamic-address") ttl=$Ttl comment=$Comment $DnsRecord;
+ }
+ } else={
+ $LogPrint info $ScriptName ("Adding new DNS entry for " . $Fqdn . ", address is " . $PeerVal->"dynamic-address" . ".");
+ /ip/dns/static/add name=$Fqdn address=($PeerVal->"dynamic-address") ttl=$Ttl comment=$Comment place-before=$PlaceBefore;
+ }
+ }
+} on-error={ }
diff --git a/ipv6-update b/ipv6-update
deleted file mode 100644
index 3223807..0000000
--- a/ipv6-update
+++ /dev/null
@@ -1,60 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: ipv6-update
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# update firewall and dns settings on IPv6 prefix change
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/ipv6-update.md
-
-:local 0 "ipv6-update";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:local PdPrefix $"pd-prefix";
-
-:global LogPrintExit2;
-:global ParseKeyValueStore;
-
-:if ([ :typeof $PdPrefix ] = "nothing") do={
- $LogPrintExit2 error $0 ("This script is supposed to run from ipv6 dhcp-client.") true;
-}
-
-:local Pool [ / ipv6 pool get [ find where prefix=$PdPrefix ] name ];
-:if ([ :len [ / ipv6 firewall address-list find where comment=("ipv6-pool-" . $Pool) ] ] = 0) do={
- / ipv6 firewall address-list add list=("ipv6-pool-" . $Pool) address=:: comment=("ipv6-pool-" . $Pool);
- :log warning ("Added 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={
- :log info ("Updating IPv6 address list with new IPv6 prefix " . $PdPrefix);
- / ipv6 firewall address-list set address=$PdPrefix $AddrList;
-
- # give the interfaces a moment to receive their addresses
- :delay 2s;
-
- :foreach ListEntry in=[ / ipv6 firewall address-list find where comment~("^ipv6-pool-" . $Pool . ",") ] do={
- :local ListEntryVal [ / ipv6 firewall address-list get $ListEntry ];
- :local Comment [ $ParseKeyValueStore ($ListEntryVal->"comment") ];
-
- :local Address [ / ipv6 address find where from-pool=$Pool interface=($Comment->"interface") ];
- :if ([ :len $Address ] = 1) do={
- :set Address [ / ipv6 address get $Address address ];
- :log info ("Updating IPv6 address list with new IPv6 prefix " . $Address . " from interface " . ($Comment->"interface"));
- / ipv6 firewall address-list set address=$Address $ListEntry;
- }
- }
-
- :foreach Record in=[ / ip dns static find where comment~("^ipv6-pool-" . $Pool . ",") ] do={
- :local RecordVal [ / ip dns static get $Record ];
- :local Comment [ $ParseKeyValueStore ($RecordVal->"comment") ];
-
- :local Prefix [ / ipv6 address get [ find where interface=($Comment->"interface") from-pool=$Pool global ] address ];
- :set Prefix [ :toip6 [ :pick $Prefix 0 [ :find $Prefix "/64" ] ] ];
- :local Address ($Prefix | ([ :toip6 ($RecordVal->"address") ] & ::ffff:ffff:ffff:ffff));
-
- :log info ("Updating DNS record for " . ($RecordVal->"name") . ($RecordVal->"regexp") . " to " . $Address);
- / ip dns static set address=$Address $Record;
- }
-}
diff --git a/ipv6-update.rsc b/ipv6-update.rsc
new file mode 100644
index 0000000..65bb959
--- /dev/null
+++ b/ipv6-update.rsc
@@ -0,0 +1,87 @@
+#!rsc by RouterOS
+# RouterOS script: ipv6-update
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# update firewall and dns settings on IPv6 prefix change
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/ipv6-update.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global ScriptLock;
+
+ :local PdPrefix $"pd-prefix";
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :if ([ :typeof $PdPrefix ] = "nothing") do={
+ $LogPrint error $ScriptName ("This script is supposed to run from ipv6 dhcp-client.");
+ :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);
+ $LogPrint warning $ScriptName ("Added ipv6 address list entry for ipv6-pool-" . $Pool);
+ }
+ :local AddrList [ /ipv6/firewall/address-list/find where comment=("ipv6-pool-" . $Pool) ];
+ :local OldPrefix [ /ipv6/firewall/address-list/get ($AddrList->0) address ];
+
+ :if ($OldPrefix != $PdPrefix) do={
+ $LogPrint info $ScriptName ("Updating IPv6 address list with new IPv6 prefix " . $PdPrefix);
+ /ipv6/firewall/address-list/set address=$PdPrefix $AddrList;
+
+ # give the interfaces a moment to receive their addresses
+ :delay 2s;
+
+ :foreach ListEntry in=[ /ipv6/firewall/address-list/find where comment~("^ipv6-pool-" . $Pool . ",") ] do={
+ :local ListEntryVal [ /ipv6/firewall/address-list/get $ListEntry ];
+ :local Comment [ $ParseKeyValueStore ($ListEntryVal->"comment") ];
+
+ :local Prefix [ /ipv6/address/find where from-pool=$Pool interface=($Comment->"interface") global ];
+ :if ([ :len $Prefix ] = 1) do={
+ :set Prefix [ /ipv6/address/get $Prefix address ];
+
+ :if ([ :typeof [ :find ($ListEntryVal->"address") "/128" ] ] = "num" ) do={
+ :set Prefix ([ :toip6 [ :pick $Prefix 0 [ :find $Prefix "/64" ] ] ] & ffff:ffff:ffff:ffff::);
+ :local Address ($ListEntryVal->"address");
+ :local Address ($Prefix | ([ :toip6 [ :pick $Address 0 [ :find $Address "/128" ] ] ] & ::ffff:ffff:ffff:ffff));
+
+ $LogPrint info $ScriptName ("Updating IPv6 address list with new IPv6 host address " . $Address . \
+ " from interface " . ($Comment->"interface"));
+ /ipv6/firewall/address-list/set address=$Address $ListEntry;
+ } else={
+ $LogPrint info $ScriptName ("Updating IPv6 address list with new IPv6 prefix " . $Prefix . \
+ " from interface " . ($Comment->"interface"));
+ /ipv6/firewall/address-list/set address=$Prefix $ListEntry;
+ }
+ }
+ }
+
+ :foreach Record in=[ /ip/dns/static/find where comment~("^ipv6-pool-" . $Pool . ",") ] do={
+ :local RecordVal [ /ip/dns/static/get $Record ];
+ :local Comment [ $ParseKeyValueStore ($RecordVal->"comment") ];
+
+ :local Prefix [ /ipv6/address/find where from-pool=$Pool interface=($Comment->"interface") global ];
+ :if ([ :len $Prefix ] = 1) do={
+ :set Prefix [ /ipv6/address/get $Prefix address ];
+ :set Prefix ([ :toip6 [ :pick $Prefix 0 [ :find $Prefix "/64" ] ] ] & ffff:ffff:ffff:ffff::);
+ :local Address ($Prefix | ([ :toip6 ($RecordVal->"address") ] & ::ffff:ffff:ffff:ffff));
+
+ $LogPrint info $ScriptName ("Updating DNS record for " . ($RecordVal->"name") . \
+ ($RecordVal->"regexp") . " to " . $Address);
+ /ip/dns/static/set address=$Address $Record;
+ }
+ }
+ }
+} on-error={ }
diff --git a/learn-mac-based-vlan b/learn-mac-based-vlan
deleted file mode 100644
index e301a47..0000000
--- a/learn-mac-based-vlan
+++ /dev/null
@@ -1,13 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: learn-mac-based-vlan
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# learn MAC address for MAC-based-VLAN
-
-:local NewVlanId 33;
-
-:if ([ :len [ / interface ethernet switch mac-based-vlan find where src-mac-address=$leaseActMAC ] ] = 0 ) do={
- :log info ("MAC-based-VLAN: learning MAC address " . $leaseActMAC . " for VLAN " . $NewVlanId . ".");
- / interface ethernet switch mac-based-vlan add src-mac-address=$leaseActMAC new-customer-vid=$NewVlanId;
-}
diff --git a/lease-script b/lease-script
deleted file mode 100644
index 28f9e20..0000000
--- a/lease-script
+++ /dev/null
@@ -1,53 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: lease-script
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# run scripts on DHCP lease
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/lease-script.md
-
-:local 0 "lease-script";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global LogPrintExit2;
-
-: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 Scripts;
-:local ScriptsAssign {
- "dhcp-to-dns";
- "collect-wireless-mac.local";
- "dhcp-lease-comment.local";
- "collect-wireless-mac.capsman";
- "dhcp-lease-comment.capsman"
-}
-:local ScriptsDeAssign {
- "dhcp-to-dns"
-}
-
-:local State "";
-:if ($leaseBound = 0) do={
- :set State "de";
- :set Scripts $ScriptsDeAssign;
-} else={
- :set Scripts $ScriptsAssign;
-}
-
-:log debug ("DHCP Server " . $leaseServerName . " " . \
- $State . "assigned lease " . $leaseActIP . " to " . $leaseActMAC);
-
-# delay a moment to update the lease table, do not run in parallel for de/assign
-:delay ((1 + $leaseBound) . "s");
-
-:foreach Script in=$Scripts do={
- :if ([ :len [ / system script find where name=$Script ] ] > 0) do={
- :log debug ("Running script from lease-script: " . $Script);
- / system script run $Script;
- }
-}
diff --git a/lease-script.rsc b/lease-script.rsc
new file mode 100644
index 0000000..8e1e8f6
--- /dev/null
+++ b/lease-script.rsc
@@ -0,0 +1,59 @@
+#!rsc by RouterOS
+# RouterOS script: lease-script
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# run scripts on DHCP lease
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/lease-script.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global Grep;
+ :global IfThenElse;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global ScriptLock;
+
+ :if ([ :typeof $leaseActIP ] = "nothing" || \
+ [ :typeof $leaseActMAC ] = "nothing" || \
+ [ :typeof $leaseServerName ] = "nothing" || \
+ [ :typeof $leaseBound ] = "nothing") do={
+ $LogPrint error $ScriptName ("This script is supposed to run from ip dhcp-server.");
+ :error false;
+ }
+
+ $LogPrint debug $ScriptName ("DHCP Server " . $leaseServerName . " " . [ $IfThenElse ($leaseBound = 0) \
+ "de" "" ] . "assigned lease " . $leaseActIP . " to " . $leaseActMAC);
+
+ :if ([ $ScriptLock $ScriptName 10 ] = false) do={
+ :error false;
+ }
+
+ :if ([ :len [ /system/script/job/find where script=$ScriptName ] ] > 1) do={
+ $LogPrint debug $ScriptName ("More invocations are waiting, exiting early.");
+ :error true;
+ }
+
+ :local RunOrder ({});
+ :foreach Script in=[ /system/script/find where source~("\n# provides: lease-script\\b") ] do={
+ :local ScriptVal [ /system/script/get $Script ];
+ :local Store [ $ParseKeyValueStore [ $Grep ($ScriptVal->"source") ("\23 provides: lease-script, ") ] ];
+
+ :set ($RunOrder->($Store->"order" . "-" . $ScriptVal->"name")) ($ScriptVal->"name");
+ }
+
+ :foreach Order,Script in=$RunOrder do={
+ :do {
+ $LogPrint debug $ScriptName ("Running script with order " . $Order . ": " . $Script);
+ /system/script/run $Script;
+ } on-error={
+ $LogPrint warning $ScriptName ("Running script '" . $Script . "' failed!");
+ }
+ }
+} on-error={ }
diff --git a/leds-day-mode b/leds-day-mode.rsc
index 4d77469..b7c6b5b 100644
--- a/leds-day-mode
+++ b/leds-day-mode.rsc
@@ -1,9 +1,9 @@
#!rsc by RouterOS
# RouterOS script: leds-day-mode
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
#
# enable LEDs
# https://git.eworm.de/cgit/routeros-scripts/about/doc/leds-mode.md
-/ system leds settings set all-leds-off=never;
+/system/leds/settings/set all-leds-off=never;
diff --git a/leds-night-mode b/leds-night-mode.rsc
index 89ac834..fb7c7a2 100644
--- a/leds-night-mode
+++ b/leds-night-mode.rsc
@@ -1,9 +1,9 @@
#!rsc by RouterOS
# RouterOS script: leds-night-mode
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
#
# disable LEDs
# https://git.eworm.de/cgit/routeros-scripts/about/doc/leds-mode.md
-/ system leds settings set all-leds-off=immediate;
+/system/leds/settings/set all-leds-off=immediate;
diff --git a/leds-toggle-mode b/leds-toggle-mode
deleted file mode 100644
index 6ee81c8..0000000
--- a/leds-toggle-mode
+++ /dev/null
@@ -1,13 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: leds-toggle-mode
-# Copyright (c) 2018-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# toggle LEDs mode
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/leds-mode.md
-
-:if ([ / system leds settings get all-leds-off ] = "never") do={
- / system leds settings set all-leds-off=immediate;
-} else={
- / system leds settings set all-leds-off=never;
-}
diff --git a/leds-toggle-mode.rsc b/leds-toggle-mode.rsc
new file mode 100644
index 0000000..136c9d1
--- /dev/null
+++ b/leds-toggle-mode.rsc
@@ -0,0 +1,13 @@
+#!rsc by RouterOS
+# RouterOS script: leds-toggle-mode
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# toggle LEDs mode
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/leds-mode.md
+
+:if ([ /system/leds/settings/get all-leds-off ] = "never") do={
+ /system/leds/settings/set all-leds-off=immediate;
+} else={
+ /system/leds/settings/set all-leds-off=never;
+}
diff --git a/log-forward b/log-forward
deleted file mode 100644
index def04e6..0000000
--- a/log-forward
+++ /dev/null
@@ -1,68 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: log-forward
-# Copyright (c) 2020-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# forward log messages via notification
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/log-forward.md
-
-:local 0 "log-forward";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global Identity;
-:global LogForwardFilter;
-:global LogForwardFilterMessage;
-:global LogForwardLast;
-:global LogForwardRateLimit;
-
-:global IfThenElse;
-:global LogPrintExit2;
-:global ScriptLock;
-:global SendNotification;
-:global SymbolForNotification;
-:global WaitFullyConnected;
-
-$ScriptLock $0;
-
-:if ([ :typeof $LogForwardRateLimit ] = "nothing") do={
- :set LogForwardRateLimit 0;
-}
-
-:if ($LogForwardRateLimit > 30) do={
- :set LogForwardRateLimit ($LogForwardRateLimit - 1);
- $LogPrintExit2 info $0 ("Rate limit in action, not forwarding logs, if any!") true;
-}
-
-$WaitFullyConnected;
-
-:local Count 0;
-:local Messages "";
-:local MessageVal;
-
-:foreach Message in=[ / log find where !(topics~$LogForwardFilter) !(message~$LogForwardFilterMessage) ] do={
- :set MessageVal [ / log get $Message ];
-
- :if ($LogForwardLast = ($MessageVal->".id")) do={
- :set Messages "";
- :set Count 0;
- } else={
- :set Messages ($Messages . "\n" . $MessageVal->"time" . " " . \
- [ :tostr ($MessageVal->"topics") ] . " " . $MessageVal->"message");
- :set Count ($Count + 1);
- }
-}
-
-:if ($Count > 0) do={
- $SendNotification ([ $SymbolForNotification "warning-sign" ] . "Log Forwarding") \
- ("The log on " . $Identity . " contains " . [ $IfThenElse ($Count = 1) \
- "this message" ("these " . $Count . " messages") ] . " after " . \
- [ / system resource get uptime ] . " uptime.\n" . $Messages);
-
- :set LogForwardRateLimit ($LogForwardRateLimit + 10);
- :set LogForwardLast ($MessageVal->".id");
-} else={
- :if ($LogForwardRateLimit > 0) do={
- :set LogForwardRateLimit ($LogForwardRateLimit - 1);
- }
-}
diff --git a/log-forward.rsc b/log-forward.rsc
new file mode 100644
index 0000000..a919d8f
--- /dev/null
+++ b/log-forward.rsc
@@ -0,0 +1,102 @@
+#!rsc by RouterOS
+# RouterOS script: log-forward
+# Copyright (c) 2020-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# forward log messages via notification
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/log-forward.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global Identity;
+ :global LogForwardFilter;
+ :global LogForwardFilterMessage;
+ :global LogForwardInclude;
+ :global LogForwardIncludeMessage;
+ :global LogForwardLast;
+ :global LogForwardRateLimit;
+
+ :global EitherOr;
+ :global HexToNum;
+ :global IfThenElse;
+ :global LogForwardFilterLogForwarding;
+ :global LogPrint;
+ :global MAX;
+ :global ScriptLock;
+ :global SendNotification2;
+ :global SymbolForNotification;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :if ([ :typeof $LogForwardRateLimit ] = "nothing") do={
+ :set LogForwardRateLimit 0;
+ }
+
+ :if ($LogForwardRateLimit > 30) do={
+ :set LogForwardRateLimit ($LogForwardRateLimit - 1);
+ $LogPrint info $ScriptName ("Rate limit in action, not forwarding logs, if any!");
+ :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 LogForwardFilterLogForwardingCached [ $EitherOr [ $LogForwardFilterLogForwarding ] ("\$^") ];
+ :foreach Message in=[ /log/find where (!(message="") and \
+ !(message~$LogForwardFilterLogForwardingCached) and \
+ !(topics~$LogForwardFilter) and !(message~$LogForwardFilterMessage)) or \
+ topics~$LogForwardInclude or message~$LogForwardIncludeMessage ] do={
+ :set MessageVal [ /log/get $Message ];
+ :local Bullet "information";
+
+ :if ($Last < [ $HexToNum ($MessageVal->".id") ]) do={
+ :local DupCount ($MessageDups->($MessageVal->"message"));
+ :if ($MessageVal->"topics" ~ "(warning)") do={
+ :set Warning true;
+ :set Bullet "large-orange-circle";
+ }
+ :if ($MessageVal->"topics" ~ "(emergency|alert|critical|error)") do={
+ :set Warning true;
+ :set Bullet "large-red-circle";
+ }
+ :if ($DupCount < 3) do={
+ :set Messages ($Messages . "\n" . [ $SymbolForNotification $Bullet ] . \
+ $MessageVal->"time" . " " . [ :tostr ($MessageVal->"topics") ] . " " . $MessageVal->"message");
+ } else={
+ :set Duplicates true;
+ }
+ :set ($MessageDups->($MessageVal->"message")) ($DupCount + 1);
+ :set Count ($Count + 1);
+ }
+ }
+
+ :if ($Count > 0) do={
+ :set LogForwardRateLimit ($LogForwardRateLimit + 10);
+
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification [ $IfThenElse ($Warning = true) "warning-sign" "memo" ] ] . \
+ "Log Forwarding"); \
+ message=("The log on " . $Identity . " contains " . [ $IfThenElse ($Count = 1) "this message" \
+ ("these " . $Count . " messages") ] . " after " . [ /system/resource/get uptime ] . " uptime." . \
+ [ $IfThenElse ($Duplicates = true) (" Multi-repeated messages have been skipped.") ] . \
+ [ $IfThenElse ($LogForwardRateLimit > 30) ("\nRate limit in action, delaying forwarding.") ] . \
+ "\n" . $Messages) });
+
+ :set LogForwardLast ($MessageVal->".id");
+ } else={
+ :set LogForwardRateLimit [ $MAX 0 ($LogForwardRateLimit - 1) ];
+ }
+} on-error={ }
diff --git a/logo.avif b/logo.avif
new file mode 100644
index 0000000..399a2f5
--- /dev/null
+++ b/logo.avif
Binary files differ
diff --git a/logo.png b/logo.png
new file mode 100644
index 0000000..d97b75d
--- /dev/null
+++ b/logo.png
Binary files differ
diff --git a/logo.svg b/logo.svg
new file mode 100644
index 0000000..a30e04e
--- /dev/null
+++ b/logo.svg
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg" width="96" height="96" version="1.1" viewBox="0 0 25.4 25.4" xmlns="http://www.w3.org/2000/svg">
+ <defs id="defs">
+ <radialGradient id="radGradDark" cx="7.5006" cy="9.4015" r="9.7854" gradientTransform="matrix(1.6107 1.0797 -.58681 .87543 .93614 -7.022)" gradientUnits="userSpaceOnUse">
+ <stop id="dark-1" stop-color="#222" offset="0"/>
+ <stop id="dark-2" stop-color="#444" offset="1"/>
+ </radialGradient>
+ <radialGradient id="radGradRed" cx="14.501" cy="10.029" r="2.6711" gradientTransform="matrix(1.3827 .62837 -.44627 .98203 -1.0744 -8.9965)" gradientUnits="userSpaceOnUse">
+ <stop id="red-1" stop-color="#a00" offset="0"/>
+ <stop id="red-2" stop-color="#c22" offset="1"/>
+ </radialGradient>
+ </defs>
+ <g id="layer1">
+ <rect id="background" x="4.5766e-15" width="25.4" height="25.4" ry="5.1528" fill="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="13.229"/>
+ <path id="hexagon" d="m17.758 12.437-1.3326 0.77071a0.94166 0.94328 0 0 1-0.94166 0l-1.3326-0.77071a0.94166 0.94328 0 0 1-0.47083-0.8169v-1.5414a0.94166 0.94328 0 0 1 0.47083-0.8169l1.3326-0.77071a0.94166 0.94328 0 0 1 0.94166 0l1.3326 0.77071a0.94166 0.94328 0 0 1 0.47083 0.8169v1.5414a0.94166 0.94328 0 0 1-0.47083 0.8169z" fill="url(#radGradRed)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width=".79375"/>
+ <g id="text" fill="url(#radGradDark)" stroke="#000" stroke-width=".1">
+ <g id="shebang" aria-label="#!">
+ <path id="path904" d="m13.767 4.8761v-1.6506h-0.88032l0.26724-2.0593h-2.0593l-0.26724 2.0593h-1.572l0.26724-2.0593h-2.0593l-0.26724 2.0593h-1.1318v1.6506h0.91175l-0.393 3.0182h-1.1004v1.6506h0.88031l-0.29868 2.2479h2.0593l0.29868-2.2479h1.572l-0.29868 2.2479h2.0593l0.29868-2.2479h1.1318v-1.6506h-0.91175l0.393-3.0182zm-3.5527 3.0182h-1.572l0.393-3.0182h1.572z"/>
+ <path id="path906" d="m17.209 0.89898h-2.6409l0.3144 6.8853s0.66885-0.28785 1.0123-0.28746c0.33937 3.865e-4 0.99985 0.28746 0.99985 0.28746z"/>
+ </g>
+ <g id="rsc" aria-label="rsc">
+ <path id="path910" d="m7.5809 12.875c-0.92632 0-1.716 0.66817-2.0804 1.7919l-0.2126-1.5641h-2.0804v8.0636h2.3993v-4.0546c0.27334-1.1997 0.68335-1.9134 1.6704-1.9134 0.25816 0 0.47075 0.04556 0.72891 0.1063l0.37964-2.3234c-0.27334-0.075928-0.50112-0.1063-0.80484-0.1063z"/>
+ <path id="path912" d="m11.954 12.845c-2.0349 0-3.2953 1.0782-3.2953 2.4601 0 1.2452 0.78965 2.0652 2.3841 2.5208 1.4578 0.41001 1.7008 0.57705 1.7008 1.1237 0 0.48594-0.44038 0.75928-1.1693 0.75928-0.78965 0-1.5337-0.3189-2.1412-0.78965l-1.1693 1.306c0.78965 0.71372 1.9741 1.1997 3.3712 1.1997 2.0045 0 3.5838-0.98706 3.5838-2.6575 0-1.4426-0.89595-2.1108-2.4904-2.5664-1.4426-0.4252-1.6552-0.60742-1.6552-1.0326 0-0.36445 0.3189-0.60742 0.97188-0.60742 0.69854 0 1.3667 0.22778 1.9893 0.62261l0.88076-1.3515c-0.7441-0.60742-1.7919-0.98706-2.9612-0.98706z"/>
+ <path id="path914" d="m19.896 12.845c-2.4145 0-3.9483 1.7919-3.9483 4.3583 0 2.5512 1.5186 4.2216 3.9938 4.2216 1.1085 0 1.9741-0.36446 2.7182-0.95669l-1.0478-1.4882c-0.57705 0.36445-0.97188 0.54668-1.5489 0.54668-0.95669 0-1.5945-0.54668-1.5945-2.3386 0-1.8071 0.59224-2.5056 1.6249-2.5056 0.54668 0 1.0174 0.18223 1.5337 0.57705l1.0326-1.4274c-0.77446-0.65298-1.64-0.98706-2.7638-0.98706z"/>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/manage-umts b/manage-umts
deleted file mode 100644
index b8426bd..0000000
--- a/manage-umts
+++ /dev/null
@@ -1,29 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: manage-umts
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# manage UMTS interface based on ethernet and wireless status
-
-:local EtherInt "en1";
-:local WlanInt "wl-station";
-:local UmtsInt "t-mobile";
-
-:local EtherStatus [ / interface ethernet get $EtherInt running ];
-:local WlanStatus [ / interface wireless get $WlanInt running ];
-
-:if ($EtherStatus = true || $WlanStatus = true) do={
- :if ([ / interface get $UmtsInt disabled ] = false) do={
- :log info ("Ethernet (" . $EtherInt . " / " . $EtherStatus . ") or " . \
- "wireless (" . $WlanInt . " / " . $WlanStatus . ") is running, " . \
- "UMTS interface " . $UmtsInt . " is enabled. Disabling...");
- / interface set disabled=yes $UmtsInt;
- }
-} else={
- :if ([ / interface get $UmtsInt disabled ] = true) do={
- :log info ("Neither ethernet (" . $EtherInt . ") nor wireless (" . \
- $WlanInt . ") interface is running, UMTS interface " . $UmtsInt . \
- " is disabled. Enabling...");
- / interface set disabled=no $UmtsInt;
- }
-}
diff --git a/mod/bridge-port-to.rsc b/mod/bridge-port-to.rsc
new file mode 100644
index 0000000..567a762
--- /dev/null
+++ b/mod/bridge-port-to.rsc
@@ -0,0 +1,66 @@
+#!rsc by RouterOS
+# RouterOS script: mod/bridge-port-to
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# reset bridge ports to default bridge
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/bridge-port-to.md
+
+:global BridgePortTo;
+
+:set BridgePortTo do={
+ :local BridgePortTo [ :tostr $1 ];
+
+ :global IfThenElse;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+
+ :local InterfaceReEnable ({});
+ :foreach BridgePort in=[ /interface/bridge/port/find where !(comment=[]) ] do={
+ :local BridgePortVal [ /interface/bridge/port/get $BridgePort ];
+ :foreach Config,BridgeDefault in=[ $ParseKeyValueStore ($BridgePortVal->"comment") ] do={
+ :if ($Config = $BridgePortTo) do={
+ :local DHCPClient [ /ip/dhcp-client/find where interface=$BridgePortVal->"interface" comment="toggle with bridge port" ];
+
+ :if ($BridgeDefault = "dhcp-client") do={
+ :if ([ :len $DHCPClient ] != 1) do={
+ $LogPrint warning $0 ([ $IfThenElse ([ :len $DHCPClient ] = 0) "Missing" "Duplicate" ] . \
+ " dhcp client configuration for interface " . $BridgePortVal->"interface" . "!");
+ :return false;
+ }
+ :local DHCPClientDisabled [ /ip/dhcp-client/get $DHCPClient disabled ];
+
+ :if ($BridgePortVal->"disabled" = false || $DHCPClientDisabled = true) do={
+ $LogPrint info $0 ("Disabling bridge port for interface " . $BridgePortVal->"interface" . ", enabling dhcp client.");
+ /interface/bridge/port/disable $BridgePort;
+ :delay 200ms;
+ /ip/dhcp-client/enable $DHCPClient;
+ }
+ } else={
+ :if ($BridgePortVal->"disabled" = true || $BridgeDefault != $BridgePortVal->"bridge") do={
+ $LogPrint info $0 ("Enabling bridge port for interface " . $BridgePortVal->"interface" . ", changing to " . $BridgePortTo . \
+ " bridge " . $BridgeDefault . ", disabling dhcp client.");
+ :if ([ :len $DHCPClient ] = 1) do={
+ /ip/dhcp-client/disable $DHCPClient;
+ :delay 200ms;
+ }
+ :local Disable [ /interface/ethernet/find where name=$BridgePortVal->"interface" ];
+ :if ([ :len $Disable ] > 0) do={
+ /interface/ethernet/disable $Disable;
+ :set InterfaceReEnable ($InterfaceReEnable, $Disable);
+ }
+ /interface/bridge/port/set disabled=no bridge=$BridgeDefault $BridgePort;
+ } else={
+ $LogPrint debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $BridgePortTo . \
+ " bridge " . $BridgeDefault . ".");
+ }
+ }
+ }
+ }
+ }
+ :if ([ :len $InterfaceReEnable ] > 0) do={
+ :delay 5s;
+ $LogPrint info $0 ("Re-enabling interfaces...");
+ /interface/ethernet/enable $InterfaceReEnable;
+ }
+}
diff --git a/mod/bridge-port-vlan.rsc b/mod/bridge-port-vlan.rsc
new file mode 100644
index 0000000..aee5ef9
--- /dev/null
+++ b/mod/bridge-port-vlan.rsc
@@ -0,0 +1,75 @@
+#!rsc by RouterOS
+# RouterOS script: mod/bridge-port-vlan
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# manage VLANs on bridge ports
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/bridge-port-vlan.md
+
+:global BridgePortVlan;
+
+:global BridgePortVlan do={
+ :local ConfigTo [ :tostr $1 ];
+
+ :global IfThenElse;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+
+ :local InterfaceReEnable ({});
+ :foreach BridgePort in=[ /interface/bridge/port/find where !(comment=[]) ] do={
+ :local BridgePortVal [ /interface/bridge/port/get $BridgePort ];
+ :foreach Config,Vlan in=[ $ParseKeyValueStore ($BridgePortVal->"comment") ] do={
+ :if ($Config = $ConfigTo) do={
+ :local DHCPClient [ /ip/dhcp-client/find where interface=$BridgePortVal->"interface" comment="toggle with bridge port" ];
+
+ :if ($Vlan = "dhcp-client") do={
+ :if ([ :len $DHCPClient ] != 1) do={
+ $LogPrint warning $0 ([ $IfThenElse ([ :len $DHCPClient ] = 0) "Missing" "Duplicate" ] . \
+ " dhcp client configuration for interface " . $BridgePortVal->"interface" . "!");
+ :return false;
+ }
+ :local DHCPClientDisabled [ /ip/dhcp-client/get $DHCPClient disabled ];
+
+ :if ($BridgePortVal->"disabled" = false || $DHCPClientDisabled = true) do={
+ $LogPrint info $0 ("Disabling bridge port for interface " . $BridgePortVal->"interface" . ", enabling dhcp client.");
+ /interface/bridge/port/disable $BridgePort;
+ :delay 200ms;
+ /ip/dhcp-client/enable $DHCPClient;
+ }
+ } else={
+ :local VlanName $Vlan;
+ :if ($Vlan != [ :tostr [ :tonum $Vlan ] ]) do={
+ :do {
+ :set $Vlan ([ /interface/bridge/vlan/get [ find where comment=$Vlan ] vlan-ids ]->0);
+ } on-error={
+ $LogPrint warning $0 ("Could not find VLAN '" . $Vlan . "' for interface " . $BridgePortVal->"interface" . "!");
+ :return false;
+ }
+ }
+ :if ($BridgePortVal->"disabled" = true || $Vlan != $BridgePortVal->"pvid") do={
+ $LogPrint info $0 ("Enabling bridge port for interface " . $BridgePortVal->"interface" . ", changing to " . $ConfigTo . \
+ " vlan " . $Vlan . [ $IfThenElse ($Vlan != $VlanName) (" (" . $VlanName . ")") ] . ", disabling dhcp client.");
+ :if ([ :len $DHCPClient ] = 1) do={
+ /ip/dhcp-client/disable $DHCPClient;
+ :delay 200ms;
+ }
+ :local Disable [ /interface/ethernet/find where name=$BridgePortVal->"interface" ];
+ :if ([ :len $Disable ] > 0) do={
+ /interface/ethernet/disable $Disable;
+ :set InterfaceReEnable ($InterfaceReEnable, $Disable);
+ }
+ /interface/bridge/port/set disabled=no pvid=$Vlan $BridgePort;
+ } else={
+ $LogPrint debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $ConfigTo . \
+ " vlan " . $Vlan . ".");
+ }
+ }
+ }
+ }
+ }
+ :if ([ :len $InterfaceReEnable ] > 0) do={
+ :delay 5s;
+ $LogPrint info $0 ("Re-enabling interfaces...");
+ /interface/ethernet/enable $InterfaceReEnable;
+ }
+}
diff --git a/mod/inspectvar.rsc b/mod/inspectvar.rsc
new file mode 100644
index 0000000..577abf3
--- /dev/null
+++ b/mod/inspectvar.rsc
@@ -0,0 +1,57 @@
+#!rsc by RouterOS
+# RouterOS script: mod/inspectvar
+# Copyright (c) 2020-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# inspect variables
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/inspectvar.md
+
+:global InspectVar;
+:global InspectVarReturn;
+
+# inspect variable and print on terminal
+:set InspectVar do={
+ :global InspectVarReturn;
+ :global PrettyPrint;
+
+ $PrettyPrint [ $InspectVarReturn $1 ];
+}
+
+# inspect variable and return formatted string
+:set InspectVarReturn do={
+ :local Input $1;
+ :local Level (0 + [ :tonum $2 ]);
+
+ :global IfThenElse;
+ :global InspectVarReturn;
+
+ :local IndentReturn do={
+ :local Prefix [ :tostr $1 ];
+ :local Value [ :tostr $2 ];
+ :local Level [ :tonum $3 ];
+
+ :local Indent "";
+ :for I from=1 to=$Level step=1 do={
+ :set Indent ($Indent . " ");
+ }
+ :return ($Indent . "-" . $Prefix . "-> " . $Value);
+ }
+
+ :local TypeOf [ :typeof $Input ];
+ :local Return [ $IndentReturn "type" $TypeOf $Level ];
+
+ :if ($TypeOf = "array") do={
+ :foreach Key,Value in=$Input do={
+ :set $Return ($Return . "\n" . \
+ [ $IndentReturn "key" $Key ($Level + 1) ] . "\n" . \
+ [ $InspectVarReturn $Value ($Level + 2) ]);
+ }
+ } else={
+ :if ($TypeOf != "nothing") do={
+ :set $Return ($Return . "\n" . \
+ [ $IndentReturn "value" [ $IfThenElse ([ :len $Input ] > 80) \
+ ([ :pick $Input 0 77 ] . "...") $Input ] $Level ]);
+ }
+ }
+ :return $Return;
+}
diff --git a/mod/ipcalc.rsc b/mod/ipcalc.rsc
new file mode 100644
index 0000000..b098b44
--- /dev/null
+++ b/mod/ipcalc.rsc
@@ -0,0 +1,50 @@
+#!rsc by RouterOS
+# RouterOS script: mod/ipcalc
+# Copyright (c) 2020-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# ip address calculation
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/ipcalc.md
+
+:global IPCalc;
+:global IPCalcReturn;
+
+# print netmask, network, min host, max host and broadcast
+:set IPCalc do={
+ :local Input [ :tostr $1 ];
+
+ :global FormatLine;
+ :global IPCalcReturn;
+ :global PrettyPrint;
+
+ :local Values [ $IPCalcReturn $1 ];
+
+ $PrettyPrint ( \
+ [ $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") ]);
+}
+
+# calculate and return netmask, network, min host, max host and broadcast
+:set IPCalcReturn do={
+ :local Input [ :tostr $1 ];
+ :local Address [ :toip [ :pick $Input 0 [ :find $Input "/" ] ] ];
+ :local Bits [ :tonum [ :pick $Input ([ :find $Input "/" ] + 1) [ :len $Input ] ] ];
+ :local Mask ((255.255.255.255 << (32 - $Bits)) & 255.255.255.255);
+
+ :local Return {
+ "address"=$Address;
+ "netmask"=$Mask;
+ "networkaddress"=($Address & $Mask);
+ "networkbits"=$Bits;
+ "network"=(($Address & $Mask) . "/" . $Bits);
+ "hostmin"=(($Address & $Mask) | 0.0.0.1);
+ "hostmax"=(($Address | ~$Mask) ^ 0.0.0.1);
+ "broadcast"=($Address | ~$Mask);
+ }
+
+ :return $Return;
+}
diff --git a/mod/notification-email.rsc b/mod/notification-email.rsc
new file mode 100644
index 0000000..0d83d69
--- /dev/null
+++ b/mod/notification-email.rsc
@@ -0,0 +1,240 @@
+#!rsc by RouterOS
+# RouterOS script: mod/notification-email
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# send notifications via e-mail
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/notification-email.md
+
+:global EMailGenerateFrom;
+:global FlushEmailQueue;
+:global LogForwardFilterLogForwarding;
+:global NotificationEMailSubject;
+:global NotificationFunctions;
+:global PurgeEMailQueue;
+:global QuotedPrintable;
+:global SendEMail;
+:global SendEMail2;
+
+# generate from-property with display name
+:set EMailGenerateFrom do={
+ :global Identity;
+
+ :global CleanName;
+
+ :local From [ /tool/e-mail/get from ];
+
+ :if ($From ~ "<.*>\$") do={
+ :return $From;
+ }
+
+ :return ([ $CleanName $Identity ] . " via routeros-scripts <" . $From . ">");
+}
+
+# flush e-mail queue
+:set FlushEmailQueue do={
+ :global EmailQueue;
+
+ :global EitherOr;
+ :global EMailGenerateFrom;
+ :global IsDNSResolving;
+ :global IsTimeSync;
+ :global LogPrint;
+
+ :local AllDone true;
+ :local QueueLen [ :len $EmailQueue ];
+ :local Scheduler [ /system/scheduler/find where name="_FlushEmailQueue" ];
+
+ :if ([ :len $Scheduler ] > 0 && [ /system/scheduler/get $Scheduler interval ] < 1m) do={
+ /system/scheduler/set interval=1m comment="Doing initial checks..." $Scheduler;
+ }
+
+ :if ([ /tool/e-mail/get last-status ] = "in-progress") do={
+ $LogPrint debug $0 ("Sending mail is currently in progress, not flushing.");
+ :return false;
+ }
+
+ :if ([ $IsTimeSync ] = false) do={
+ $LogPrint debug $0 ("Time is not synced, not flushing.");
+ :return false;
+ }
+
+ :local EMailSettings [ /tool/e-mail/get ];
+ :if ([ :typeof [ :toip ($EMailSettings->"server") ] ] != "ip" && [ $IsDNSResolving ] = false) do={
+ $LogPrint debug $0 ("Server address is a DNS name and resolving fails, not flushing.");
+ :return false;
+ }
+
+ :if ([ :len $Scheduler ] > 0 && $QueueLen = 0) do={
+ $LogPrint warning $0 ("Flushing E-Mail messages from scheduler, but queue is empty.");
+ }
+
+ /system/scheduler/set interval=([ $EitherOr $QueueLen 1 ] . "m") comment="Sending..." $Scheduler;
+
+ :foreach Id,Message in=$EmailQueue do={
+ :if ([ :typeof $Message ] = "array" ) do={
+ :local Attach ({});
+ :while ([ /tool/e-mail/get last-status ] = "in-progress") do={ :delay 1s; }
+ :foreach File in=[ :toarray [ $EitherOr ($Message->"attach") "" ] ] do={
+ :if ([ :len [ /file/find where name=$File ] ] = 1) do={
+ :set Attach ($Attach, $File);
+ } else={
+ $LogPrint warning $0 ("File '" . $File . "' does not exist, can not attach.");
+ }
+ }
+ /tool/e-mail/send from=[ $EMailGenerateFrom ] to=($Message->"to") cc=($Message->"cc") \
+ subject=($Message->"subject") body=($Message->"body") file=$Attach;
+ :local Wait true;
+ :do {
+ :delay 1s;
+ :local Status [ /tool/e-mail/get last-status ];
+ :if ($Status = "succeeded") do={
+ :set ($EmailQueue->$Id);
+ :set Wait false;
+ :if (($Message->"remove-attach") = true) do={
+ :foreach File in=$Attach do={
+ /file/remove $File;
+ }
+ }
+ }
+ :if ($Status = "failed") do={
+ :set AllDone false;
+ :set Wait false;
+ }
+ } while=($Wait = true);
+ }
+ }
+
+ :if ($AllDone = true && $QueueLen = [ :len $EmailQueue ]) do={
+ /system/scheduler/remove $Scheduler;
+ :set EmailQueue;
+ } else={
+ /system/scheduler/set interval=1m comment="Waiting for retry..." $Scheduler;
+ }
+}
+
+# generate filter for log-forward
+:set LogForwardFilterLogForwarding do={
+ :global EscapeForRegEx;
+ :global NotificationEMailSubject;
+ :global SymbolForNotification;
+
+ :return ("^Error sending e-mail <(" . \
+ [ $EscapeForRegEx [ $NotificationEMailSubject ([ $SymbolForNotification \
+ "memo" ] . "Log Forwarding") ] ] . "|" . \
+ [ $EscapeForRegEx [ $NotificationEMailSubject ([ $SymbolForNotification \
+ "warning-sign" ] . "Log Forwarding") ] ] . ")>:");
+}
+
+# generate the e-mail subject
+:set NotificationEMailSubject do={
+ :global Identity;
+ :global IdentityExtra;
+
+ :global QuotedPrintable;
+
+ :return [ $QuotedPrintable ("[" . $IdentityExtra . $Identity . "] " . $1) ];
+}
+
+# send notification via e-mail - expects one array argument
+:set ($NotificationFunctions->"email") do={
+ :local Notification $1;
+
+ :global EmailGeneralTo;
+ :global EmailGeneralToOverride;
+ :global EmailGeneralCc;
+ :global EmailGeneralCcOverride;
+ :global EmailQueue;
+
+ :global EitherOr;
+ :global IfThenElse;
+ :global NotificationEMailSignature;
+ :global NotificationEMailSubject;
+
+ :local To [ $EitherOr ($EmailGeneralToOverride->($Notification->"origin")) $EmailGeneralTo ];
+ :local Cc [ $EitherOr ($EmailGeneralCcOverride->($Notification->"origin")) $EmailGeneralCc ];
+
+ :local EMailSettings [ /tool/e-mail/get ];
+ :if ([ :len $To ] = 0 || ($EMailSettings->"server") = "0.0.0.0" || ($EMailSettings->"from") = "<>") do={
+ :return false;
+ }
+
+ :if ([ :typeof $EmailQueue ] = "nothing") do={
+ :set EmailQueue ({});
+ }
+ :local Signature [ $EitherOr [ $NotificationEMailSignature ] [ /system/note/get note ] ];
+ :set ($EmailQueue->[ :len $EmailQueue ]) {
+ to=$To; cc=$Cc;
+ subject=[ $NotificationEMailSubject ($Notification->"subject") ];
+ body=(($Notification->"message") . \
+ [ $IfThenElse ([ :len ($Notification->"link") ] > 0) ("\n\n" . ($Notification->"link")) "" ] . \
+ [ $IfThenElse ([ :len $Signature ] > 0) ("\n-- \n" . $Signature) "" ]); \
+ attach=($Notification->"attach"); remove-attach=($Notification->"remove-attach") };
+ :if ([ :len [ /system/scheduler/find where name="_FlushEmailQueue" ] ] = 0) do={
+ /system/scheduler/add name="_FlushEmailQueue" interval=1s start-time=startup \
+ comment="Queuing new mail..." on-event=(":global FlushEmailQueue; \$FlushEmailQueue;");
+ }
+}
+
+# purge the e-mail queue
+:set PurgeEMailQueue do={
+ :global EmailQueue;
+
+ /system/scheduler/remove [ find where name="_FlushEmailQueue" ];
+ :set EmailQueue;
+}
+
+# convert string to quoted-printable
+:global QuotedPrintable do={
+ :local Input [ :tostr $1 ];
+
+ :global CharacterMultiply;
+
+ :if ([ :len $Input ] = 0) do={
+ :return $Input;
+ }
+
+ :local Return "";
+ :local Chars ( \
+ "\00\01\02\03\04\05\06\07\08\09\0A\0B\0C\0D\0E\0F\10\11\12\13\14\15\16\17\18\19\1A\1B\1C\1D\1E\1F" . \
+ [ $CharacterMultiply ("\00") 29 ] . "=\00?" . [ $CharacterMultiply ("\00") 63 ] . "\7F" . \
+ "\80\81\82\83\84\85\86\87\88\89\8A\8B\8C\8D\8E\8F\90\91\92\93\94\95\96\97\98\99\9A\9B\9C\9D\9E\9F" . \
+ "\A0\A1\A2\A3\A4\A5\A6\A7\A8\A9\AA\AB\AC\AD\AE\AF\B0\B1\B2\B3\B4\B5\B6\B7\B8\B9\BA\BB\BC\BD\BE\BF" . \
+ "\C0\C1\C2\C3\C4\C5\C6\C7\C8\C9\CA\CB\CC\CD\CE\CF\D0\D1\D2\D3\D4\D5\D6\D7\D8\D9\DA\DB\DC\DD\DE\DF" . \
+ "\E0\E1\E2\E3\E4\E5\E6\E7\E8\E9\EA\EB\EC\ED\EE\EF\F0\F1\F2\F3\F4\F5\F6\F7\F8\F9\FA\FB\FC\FD\FE\FF");
+ :local Hex "0123456789ABCDEF";
+
+ :for I from=0 to=([ :len $Input ] - 1) do={
+ :local Char [ :pick $Input $I ];
+ :local Replace [ :find $Chars $Char ];
+
+ :if ([ :typeof $Replace ] = "num") do={
+ :set Char ("=" . [ :pick $Hex ($Replace / 16)] . [ :pick $Hex ($Replace % 16) ]);
+ }
+ :set Return ($Return . $Char);
+ }
+
+ :if ($Input = $Return) do={
+ :return $Input;
+ }
+
+ :return ("=?utf-8?Q?" . $Return . "?=");
+}
+
+# send notification via e-mail - expects at least two string arguments
+:set SendEMail do={
+ :global SendEMail2;
+
+ $SendEMail2 ({ subject=$1; message=$2; link=$3 });
+}
+
+# send notification via e-mail - expects one array argument
+:set SendEMail2 do={
+ :local Notification $1;
+
+ :global NotificationFunctions;
+
+ ($NotificationFunctions->"email") ("\$NotificationFunctions->\"email\"") $Notification;
+}
diff --git a/mod/notification-matrix.rsc b/mod/notification-matrix.rsc
new file mode 100644
index 0000000..aa95841
--- /dev/null
+++ b/mod/notification-matrix.rsc
@@ -0,0 +1,260 @@
+#!rsc by RouterOS
+# RouterOS script: mod/notification-matrix
+# Copyright (c) 2013-2024 Michael Gisbers <michael@gisbers.de>
+# Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# send notifications via Matrix
+# https://git.eworm.de/cgit/routeros-scripts/about/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={
+ :global MatrixQueue;
+
+ :global IsFullyConnected;
+ :global LogPrint;
+
+ :if ([ $IsFullyConnected ] = false) do={
+ $LogPrint debug $0 ("System is not fully connected, not flushing.");
+ :return false;
+ }
+
+ :local AllDone true;
+ :local QueueLen [ :len $MatrixQueue ];
+
+ :if ([ :len [ /system/scheduler/find where name="_FlushMatrixQueue" ] ] > 0 && $QueueLen = 0) do={
+ $LogPrint warning $0 ("Flushing Matrix messages from scheduler, but queue is empty.");
+ }
+
+ :foreach Id,Message in=$MatrixQueue do={
+ :if ([ :typeof $Message ] = "array" ) do={
+ :do {
+ /tool/fetch check-certificate=yes-without-crl output=none http-method=post \
+ ("https://" . $Message->"homeserver" . "/_matrix/client/r0/rooms/" . $Message->"room" . \
+ "/send/m.room.message?access_token=" . $Message->"accesstoken") \
+ http-data=("{ \"msgtype\": \"m.text\", \"body\": \"" . $Message->"plain" . "\"," . \
+ "\"format\": \"org.matrix.custom.html\", \"formatted_body\": \"" . \
+ $Message->"formatted" . "\" }") as-value;
+ :set ($MatrixQueue->$Id);
+ } on-error={
+ $LogPrint debug $0 ("Sending queued Matrix message failed.");
+ :set AllDone false;
+ }
+ }
+ }
+
+ :if ($AllDone = true && $QueueLen = [ :len $MatrixQueue ]) do={
+ /system/scheduler/remove [ find where name="_FlushMatrixQueue" ];
+ :set MatrixQueue;
+ }
+}
+
+# send notification via Matrix - expects one array argument
+:set ($NotificationFunctions->"matrix") do={
+ :local Notification $1;
+
+ :global Identity;
+ :global IdentityExtra;
+ :global MatrixAccessToken;
+ :global MatrixAccessTokenOverride;
+ :global MatrixHomeServer;
+ :global MatrixHomeServerOverride;
+ :global MatrixQueue;
+ :global MatrixRoom;
+ :global MatrixRoomOverride;
+
+ :global EitherOr;
+ :global LogPrint;
+ :global SymbolForNotification;
+
+ :local PrepareText do={
+ :local Input [ :tostr $1 ];
+
+ :if ([ :len $Input ] = 0) do={
+ :return "";
+ }
+
+ :local Return "";
+ :local Chars {
+ "plain"={ "\\"; "\""; "\n" };
+ "format"={ "\\"; "\""; "\n"; "&"; "<"; ">" };
+ }
+ :local Subs {
+ "plain"={ "\\\\"; "\\\""; "\\n" };
+ "format"={ "\\\\"; "&quot;"; "<br/>"; "&amp;"; "&lt;"; "&gt;" };
+ }
+
+ :for I from=0 to=([ :len $Input ] - 1) do={
+ :local Char [ :pick $Input $I ];
+ :local Replace [ :find ($Chars->$2) $Char ];
+
+ :if ([ :typeof $Replace ] = "num") do={
+ :set Char ($Subs->$2->$Replace);
+ }
+ :set Return ($Return . $Char);
+ }
+
+ :return $Return;
+ }
+
+ :local AccessToken [ $EitherOr ($MatrixAccessTokenOverride->($Notification->"origin")) $MatrixAccessToken ];
+ :local HomeServer [ $EitherOr ($MatrixHomeServerOverride->($Notification->"origin")) $MatrixHomeServer ];
+ :local Room [ $EitherOr ($MatrixRoomOverride->($Notification->"origin")) $MatrixRoom ];
+
+ :if ([ :len $AccessToken ] = 0 || [ :len $HomeServer ] = 0 || [ :len $Room ] = 0) do={
+ :return false;
+ }
+
+ :local Plain [ $PrepareText ("## [" . $IdentityExtra . $Identity . "] " . \
+ ($Notification->"subject") . "\n```\n" . ($Notification->"message") . "\n```") "plain" ];
+ :local Formatted ("<h2>" . [ $PrepareText ("[" . $IdentityExtra . $Identity . "] " . \
+ ($Notification->"subject")) "format" ] . "</h2>" . "<pre><code>" . \
+ [ $PrepareText ($Notification->"message") "format" ] . "</code></pre>");
+ :if ([ :len ($Notification->"link") ] > 0) do={
+ :set Plain ($Plain . "\\n" . [ $SymbolForNotification "link" ] . \
+ [ $PrepareText ("[" . $Notification->"link" . "](" . $Notification->"link" . ")") "plain" ]);
+ :set Formatted ($Formatted . "<br/>" . [ $SymbolForNotification "link" ] . \
+ "<a href=\\\"" . [ $PrepareText ($Notification->"link") "format" ] . "\\\">" . \
+ [ $PrepareText ($Notification->"link") "format" ] . "</a>");
+ }
+
+ :do {
+ /tool/fetch check-certificate=yes-without-crl output=none http-method=post \
+ ("https://" . $HomeServer . "/_matrix/client/r0/rooms/" . $Room . \
+ "/send/m.room.message?access_token=" . $AccessToken) \
+ http-data=("{ \"msgtype\": \"m.text\", \"body\": \"" . $Plain . "\"," . \
+ "\"format\": \"org.matrix.custom.html\", \"formatted_body\": \"" . \
+ $Formatted . "\" }") as-value;
+ } on-error={
+ $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 \
+ on-event=(":global FlushMatrixQueue; \$FlushMatrixQueue;");
+ }
+ }
+}
+
+# purge the Matrix queue
+:set PurgeMatrixQueue do={
+ :global MatrixQueue;
+
+ /system/scheduler/remove [ find where name="_FlushMatrixQueue" ];
+ :set MatrixQueue;
+}
+
+# send notification via Matrix - expects at least two string arguments
+:set SendMatrix do={
+ :global SendMatrix2;
+
+ $SendMatrix2 ({ subject=$1; message=$2; link=$3 });
+}
+
+# send notification via Matrix - expects one array argument
+:set SendMatrix2 do={
+ :local Notification $1;
+
+ :global NotificationFunctions;
+
+ ($NotificationFunctions->"matrix") ("\$NotificationFunctions->\"matrix\"") $Notification;
+}
+
+# setup - get home server and access token
+:set SetupMatrixAuthenticate do={
+ :local User [ :tostr $1 ];
+ :local Pass [ :tostr $2 ];
+
+ :global CharacterReplace;
+ :global LogPrint;
+ :global ParseJson;
+
+ :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 \
+ ("https://" . $Domain . "/.well-known/matrix/client") as-value ]->"data");
+ :set MatrixHomeServer ([ $ParseJson ([ $ParseJson [ $CharacterReplace $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-method=post http-data=("{\"type\":\"m.login.password\", \"user\":\"" . $User . "\", \"password\":\"" . $Pass . "\"}") \
+ ("https://" . $MatrixHomeServer . "/_matrix/client/r0/login") as-value ]->"data");
+ :set MatrixAccessToken ([ $ParseJson $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/set global-config-overlay source=([ get global-config-overlay source ] . "\n" . \
+ ":global MatrixHomeServer \"" . $MatrixHomeServer . "\";\n" . \
+ ":global MatrixAccessToken \"" . $MatrixAccessToken . "\";\n");
+ $LogPrint info $0 ("Appended configuration to global-config-overlay. Now create and join a room, please!");
+ } on-error={
+ $LogPrint error $0 ("Failed appending configuration to global-config-overlay!");
+ :return false;
+ }
+}
+
+# setup - join a room
+:set SetupMatrixJoinRoom do={
+ :global MatrixRoom [ :tostr $1 ];
+
+ :global LogPrint;
+ :global UrlEncode;
+
+ :global MatrixAccessToken;
+ :global MatrixHomeServer;
+ :global MatrixRoom;
+
+ :do {
+ /tool/fetch check-certificate=yes-without-crl output=none \
+ 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 {
+ /system/script/set global-config-overlay source=([ get global-config-overlay source ] . "\n" . \
+ ":global MatrixRoom \"" . $MatrixRoom . "\";\n");
+ $LogPrint info $0 ("Appended configuration to global-config-overlay. Please review and cleanup!");
+ } on-error={
+ $LogPrint error $0 ("Failed appending configuration to global-config-overlay!");
+ :return false;
+ }
+}
diff --git a/mod/notification-ntfy.rsc b/mod/notification-ntfy.rsc
new file mode 100644
index 0000000..6d48a59
--- /dev/null
+++ b/mod/notification-ntfy.rsc
@@ -0,0 +1,136 @@
+#!rsc by RouterOS
+# RouterOS script: mod/notification-ntfy
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# send notifications via Ntfy (ntfy.sh)
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/notification-ntfy.md
+
+:global FlushNtfyQueue;
+:global NotificationFunctions;
+:global PurgeNtfyQueue;
+:global SendNtfy;
+:global SendNtfy2;
+
+# flush ntfy queue
+:set FlushNtfyQueue do={
+ :global NtfyQueue;
+ :global NtfyMessageIDs;
+
+ :global IsFullyConnected;
+ :global LogPrint;
+
+ :if ([ $IsFullyConnected ] = false) do={
+ $LogPrint debug $0 ("System is not fully connected, not flushing.");
+ :return false;
+ }
+
+ :local AllDone true;
+ :local QueueLen [ :len $NtfyQueue ];
+
+ :if ([ :len [ /system/scheduler/find where name="_FlushNtfyQueue" ] ] > 0 && $QueueLen = 0) do={
+ $LogPrint warning $0 ("Flushing Ntfy messages from scheduler, but queue is empty.");
+ }
+
+ :foreach Id,Message in=$NtfyQueue do={
+ :if ([ :typeof $Message ] = "array" ) do={
+ :do {
+ /tool/fetch check-certificate=yes-without-crl output=none http-method=post \
+ ($Message->"url") http-header-field=($Message->"headers") http-data=($Message->"text") 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;
+ }
+}
+
+# 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 NtfyTopic;
+ :global NtfyTopicOverride;
+
+ :global CertificateAvailable;
+ :global EitherOr;
+ :global IfThenElse;
+ :global LogPrint;
+ :global SymbolForNotification;
+ :global UrlEncode;
+
+ :local Server [ $EitherOr ($NtfyServerOverride->($Notification->"origin")) $NtfyServer ];
+ :local Topic [ $EitherOr ($NtfyTopicOverride->($Notification->"origin")) $NtfyTopic ];
+
+ :if ([ :len $Topic ] = 0) do={
+ :return false;
+ }
+
+ :local Url ("https://" . $NtfyServer . "/" . [ $UrlEncode $NtfyTopic ]);
+ :local Headers ({ ("Priority: " . [ $IfThenElse ($Notification->"silent") "low" "default" ]); \
+ ("Title: " . "[" . $IdentityExtra . $Identity . "] " . ($Notification->"subject")) });
+ :local Text (($Notification->"message") . "\n");
+ :if ([ :len ($Notification->"link") ] > 0) do={
+ :set Text ($Text . "\n" . [ $SymbolForNotification "link" ] . ($Notification->"link"));
+ }
+
+ :do {
+ :if ($NtfyServer = "ntfy.sh") do={
+ :if ([ $CertificateAvailable "R3" ] = false) do={
+ $LogPrint warning $0 ("Downloading required certificate failed.");
+ :error false;
+ }
+ }
+ /tool/fetch check-certificate=yes-without-crl output=none http-method=post \
+ $Url http-header-field=$Headers http-data=$Text 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={
+ :global SendNtfy2;
+
+ $SendNtfy2 ({ subject=$1; message=$2; link=$3; silent=$4 });
+}
+
+# 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
new file mode 100644
index 0000000..506ec80
--- /dev/null
+++ b/mod/notification-telegram.rsc
@@ -0,0 +1,188 @@
+#!rsc by RouterOS
+# RouterOS script: mod/notification-telegram
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# send notifications via Telegram
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/notification-telegram.md
+
+:global FlushTelegramQueue;
+:global NotificationFunctions;
+:global PurgeTelegramQueue;
+:global SendTelegram;
+:global SendTelegram2;
+
+# flush telegram queue
+:set FlushTelegramQueue do={
+ :global TelegramQueue;
+ :global TelegramMessageIDs;
+
+ :global IsFullyConnected;
+ :global LogPrint;
+ :global ParseJson;
+ :global UrlEncode;
+
+ :if ([ $IsFullyConnected ] = false) do={
+ $LogPrint debug $0 ("System is not fully connected, not flushing.");
+ :return false;
+ }
+
+ :local AllDone true;
+ :local QueueLen [ :len $TelegramQueue ];
+
+ :if ([ :len [ /system/scheduler/find where name="_FlushTelegramQueue" ] ] > 0 && $QueueLen = 0) do={
+ $LogPrint warning $0 ("Flushing Telegram messages from scheduler, but queue is empty.");
+ }
+
+ :foreach Id,Message in=$TelegramQueue do={
+ :if ([ :typeof $Message ] = "array" ) do={
+ :do {
+ :local Data ([ /tool/fetch check-certificate=yes-without-crl output=user http-method=post \
+ ("https://api.telegram.org/bot" . ($Message->"tokenid") . "/sendMessage") \
+ http-data=("chat_id=" . ($Message->"chatid") . "&disable_notification=" . ($Message->"silent") . \
+ "&reply_to_message_id=" . ($Message->"replyto") . "&disable_web_page_preview=true" . \
+ "&parse_mode=MarkdownV2&text=" . [ $UrlEncode ($Message->"text") ]) as-value ]->"data");
+ :set ($TelegramQueue->$Id);
+ :set ($TelegramMessageIDs->([ $ParseJson ([ $ParseJson $Data ]->"result") ]->"message_id")) 1;
+ } on-error={
+ $LogPrint debug $0 ("Sending queued Telegram message failed.");
+ :set AllDone false;
+ }
+ }
+ }
+
+ :if ($AllDone = true && $QueueLen = [ :len $TelegramQueue ]) do={
+ /system/scheduler/remove [ find where name="_FlushTelegramQueue" ];
+ :set TelegramQueue;
+ }
+}
+
+# send notification via telegram - expects one array argument
+:set ($NotificationFunctions->"telegram") do={
+ :local Notification $1;
+
+ :global Identity;
+ :global IdentityExtra;
+ :global TelegramChatId;
+ :global TelegramChatIdOverride;
+ :global TelegramMessageIDs;
+ :global TelegramQueue;
+ :global TelegramTokenId;
+ :global TelegramTokenIdOverride;
+
+ :global CertificateAvailable;
+ :global CharacterReplace;
+ :global EitherOr;
+ :global IfThenElse;
+ :global LogPrint;
+ :global ParseJson;
+ :global SymbolForNotification;
+ :global UrlEncode;
+
+ :local EscapeMD do={
+ :global CharacterReplace;
+ :global IfThenElse;
+
+ :local Return $1;
+ :local Chars {
+ "body"={ "\\"; "`" };
+ "plain"={ "_"; "*"; "["; "]"; "("; ")"; "~"; "`"; ">";
+ "#"; "+"; "-"; "="; "|"; "{"; "}"; "."; "!" };
+ }
+ :foreach Char in=($Chars->$2) do={
+ :set Return [ $CharacterReplace $Return $Char ("\\" . $Char) ];
+ }
+
+ :if ($2 = "body") do={
+ :return ("```\n" . $Return . "\n```");
+ }
+
+ :return $Return;
+ }
+
+ :local ChatId [ $EitherOr ($Notification->"chatid") \
+ [ $EitherOr ($TelegramChatIdOverride->($Notification->"origin")) $TelegramChatId ] ];
+ :local TokenId [ $EitherOr ($TelegramTokenIdOverride->($Notification->"origin")) $TelegramTokenId ];
+
+ :if ([ :len $TokenId ] = 0 || [ :len $ChatId ] = 0) do={
+ :return false;
+ }
+
+ :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 LenSum ($LenSubject + $LenMessage + $LenLink);
+ :if ($LenSum > 3968) do={
+ :set Text ($Text . [ $EscapeMD ([ :pick ($Notification->"message") 0 (3840 - $LenSubject - $LenLink) ] . "...") "body" ]);
+ :set Truncated true;
+ } else={
+ :set Text ($Text . [ $EscapeMD ($Notification->"message") "body" ]);
+ }
+ :if ($LenLink > 0) do={
+ :set Text ($Text . "\n" . [ $SymbolForNotification "link" ] . [ $EscapeMD ($Notification->"link") "plain" ]);
+ }
+ :if ($Truncated = true) do={
+ :set Text ($Text . "\n" . [ $SymbolForNotification "scissors" ] . \
+ [ $EscapeMD ("The message was too long and has been truncated, cut off " . \
+ (($LenSum - [ :len $Text ]) * 100 / $LenSum) . "%!") "plain" ]);
+ }
+
+ :do {
+ :if ([ $CertificateAvailable "Go Daddy Secure Certificate Authority - G2" ] = false) do={
+ $LogPrint warning $0 ("Downloading required certificate failed.");
+ :error false;
+ }
+ :local Data ([ /tool/fetch check-certificate=yes-without-crl output=user http-method=post \
+ ("https://api.telegram.org/bot" . $TokenId . "/sendMessage") \
+ http-data=("chat_id=" . $ChatId . "&disable_notification=" . ($Notification->"silent") . \
+ "&reply_to_message_id=" . ($Notification->"replyto") . "&disable_web_page_preview=true" . \
+ "&parse_mode=MarkdownV2&text=" . [ $UrlEncode $Text ]) as-value ]->"data");
+ :set ($TelegramMessageIDs->([ $ParseJson ([ $ParseJson $Data ]->"result") ]->"message_id")) 1;
+ } on-error={
+ $LogPrint info $0 ("Failed sending telegram notification! Queuing...");
+
+ :if ([ :typeof $TelegramQueue ] = "nothing") do={
+ :set TelegramQueue ({});
+ }
+ :set Text ($Text . "\n" . [ $SymbolForNotification "alarm-clock" ] . \
+ [ $EscapeMD ("This message was queued since " . [ /system/clock/get date ] . \
+ " " . [ /system/clock/get time ] . " and may be obsolete.") "plain" ]);
+ :set ($TelegramQueue->[ :len $TelegramQueue ]) { chatid=$ChatId; tokenid=$TokenId;
+ text=$Text; silent=($Notification->"silent"); replyto=($Notification->"replyto") };
+ :if ([ :len [ /system/scheduler/find where name="_FlushTelegramQueue" ] ] = 0) do={
+ /system/scheduler/add name="_FlushTelegramQueue" interval=1m start-time=startup \
+ on-event=(":global FlushTelegramQueue; \$FlushTelegramQueue;");
+ }
+ }
+}
+
+# purge the Telegram queue
+:set PurgeTelegramQueue do={
+ :global TelegramQueue;
+
+ /system/scheduler/remove [ find where name="_FlushTelegramQueue" ];
+ :set TelegramQueue;
+}
+
+# send notification via telegram - expects at least two string arguments
+:set SendTelegram do={
+ :global SendTelegram2;
+
+ $SendTelegram2 ({ subject=$1; message=$2; link=$3; silent=$4 });
+}
+
+# send notification via telegram - expects one array argument
+:set SendTelegram2 do={
+ :local Notification $1;
+
+ :global NotificationFunctions;
+
+ ($NotificationFunctions->"telegram") ("\$NotificationFunctions->\"telegram\"") $Notification;
+}
diff --git a/mod/scriptrunonce.rsc b/mod/scriptrunonce.rsc
new file mode 100644
index 0000000..85d465a
--- /dev/null
+++ b/mod/scriptrunonce.rsc
@@ -0,0 +1,50 @@
+#!rsc by RouterOS
+# RouterOS script: mod/scriptrunonece
+# Copyright (c) 2020-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# download script and run it once
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/scriptrunonce.md
+
+:global ScriptRunOnce;
+
+# fetch and run script(s) once
+:set ScriptRunOnce do={
+ :local Scripts [ :toarray $1 ];
+
+ :global ScriptRunOnceBaseUrl;
+ :global ScriptRunOnceUrlSuffix;
+
+ :global LogPrint;
+ :global ValidateSyntax;
+
+ :foreach Script in=$Scripts do={
+ :if (!($Script ~ "^(ftp|https?|sftp)://")) do={
+ :if ([ :len $ScriptRunOnceBaseUrl ] = 0) do={
+ $LogPrint warning $0 ("Script '" . $Script . "' is not an url and base url is not available.");
+ :return false;
+ }
+ :set Script ($ScriptRunOnceBaseUrl . $Script . ".rsc" . $ScriptRunOnceUrlSuffix);
+ }
+
+ :local Source;
+ :do {
+ :set Source ([ /tool/fetch check-certificate=yes-without-crl $Script output=user as-value ]->"data");
+ } on-error={
+ $LogPrint warning $0 ("Failed fetching script '" . $Script . "'!");
+ }
+
+ :if ([ :len $Source ] > 0) do={
+ :if ([ $ValidateSyntax $Source ] = true) do={
+ :do {
+ $LogPrint info $0 ("Running script '" . $Script . "' now.");
+ [ :parse $Source ];
+ } on-error={
+ $LogPrint warning $0 ("The script '" . $Script . "' failed to run!");
+ }
+ } else={
+ $LogPrint warning $0 ("The script '" . $Script . "' failed syntax validation!");
+ }
+ }
+ }
+}
diff --git a/mod/ssh-keys-import.rsc b/mod/ssh-keys-import.rsc
new file mode 100644
index 0000000..6716958
--- /dev/null
+++ b/mod/ssh-keys-import.rsc
@@ -0,0 +1,112 @@
+#!rsc by RouterOS
+# RouterOS script: mod/ssh-keys-import
+# Copyright (c) 2020-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# import ssh keys for public key authentication
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/ssh-keys-import.md
+
+:global SSHKeysImport;
+:global SSHKeysImportFile;
+
+# import single key passed as string
+:set SSHKeysImport do={
+ :local Key [ :tostr $1 ];
+ :local User [ :tostr $2 ];
+
+ :global CharacterReplace;
+ :global GetRandom20CharAlNum;
+ :global LogPrint;
+ :global MkDir;
+ :global WaitForFile;
+
+ :if ([ :len $Key ] = 0 || [ :len $User ] = 0) do={
+ $LogPrint warning $0 ("Missing argument(s), please pass key and user!");
+ :return false;
+ }
+
+ :if ([ :len [ /user/find where name=$User ] ] = 0) do={
+ $LogPrint warning $0 ("User '" . $User . "' does not exist.");
+ :return false;
+ }
+
+ :local KeyVal [ :toarray [ $CharacterReplace $Key " " "," ] ];
+ :if (!($KeyVal->0 = "ssh-ed25519" || $KeyVal->0 = "ssh-rsa")) do={
+ $LogPrint warning $0 ("SSH key of type '" . $KeyVal->0 . "' is not supported.");
+ :return false;
+ }
+
+ :if ([ $MkDir "tmpfs/ssh-keys-import" ] = false) do={
+ $LogPrint warning $0 ("Creating directory 'tmpfs/ssh-keys-import' failed!");
+ :return false;
+ }
+
+ :local FingerPrintMD5 [ :convert from=base64 transform=md5 to=hex ($KeyVal->1) ];
+
+ :if ([ :len [ /user/ssh-keys/find where user=$User key-owner~("\\bmd5=" . $FingerPrintMD5 . "\\b") ] ] > 0) do={
+ $LogPrint warning $0 ("The ssh public key (MD5:" . $FingerPrintMD5 . \
+ ") is already available for user '" . $User . "'.");
+ :return false;
+ }
+
+ :local FileName ("tmpfs/ssh-keys-import/key-" . [ $GetRandom20CharAlNum 6 ] . ".pub");
+ /file/add name=$FileName contents=($Key . ", md5=" . $FingerPrintMD5);
+ $WaitForFile $FileName;
+
+ :do {
+ /user/ssh-keys/import public-key-file=$FileName user=$User;
+ $LogPrint info $0 ("Imported ssh public key (" . $KeyVal->2 . ", " . $KeyVal->0 . ", " . \
+ "MD5:" . $FingerPrintMD5 . ") for user '" . $User . "'.");
+ } on-error={
+ $LogPrint warning $0 ("Failed importing key.");
+ :return false;
+ }
+}
+
+# import keys from a file
+:set SSHKeysImportFile do={
+ :local FileName [ :tostr $1 ];
+ :local User [ :tostr $2 ];
+
+ :global CharacterReplace;
+ :global EitherOr;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global SSHKeysImport;
+
+ :if ([ :len $FileName ] = 0 || [ :len $User ] = 0) do={
+ $LogPrint warning $0 ("Missing argument(s), please pass file name and user!");
+ :return false;
+ }
+
+ :local File [ /file/find where name=$FileName ];
+ :if ([ :len $File ] = 0) do={
+ $LogPrint warning $0 ("File '" . $FileName . "' does not exist.");
+ :return false;
+ }
+ :local Keys ([ /file/get $FileName contents ] . "\n");
+
+ :do {
+ :local Continue false;
+ :local Line [ :pick $Keys 0 [ :find $Keys "\n" ] ];
+ :set Keys [ :pick $Keys ([ :find $Keys "\n" ] + 1) [ :len $Keys ] ];
+ :local KeyVal [ :toarray [ $CharacterReplace $Line " " "," ] ];
+ :if ($KeyVal->0 = "ssh-ed25519" || $KeyVal->0 = "ssh-rsa") do={
+ :do {
+ $SSHKeysImport $Line $User;
+ } on-error={
+ $LogPrint warning $0 ("Failed importing key for user '" . $User . "'.");
+ }
+ :set Continue true;
+ }
+ :if ($Continue = false && $KeyVal->0 = "#") do={
+ :set User [ $EitherOr ([ $ParseKeyValueStore [ :pick $Line 2 [ :len $Line ] ] ]->"user") $User ];
+ :set Continue true;
+ }
+ :if ($Continue = false && [ :len ($KeyVal->0) ] > 0) do={
+ $LogPrint warning $0 ("SSH key of type '" . $KeyVal->0 . "' is not supported.");
+ }
+ } while=([ :len $Keys ] > 0);
+}
diff --git a/mode-button b/mode-button
deleted file mode 100644
index e2da374..0000000
--- a/mode-button
+++ /dev/null
@@ -1,76 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: mode-button
-# Copyright (c) 2018-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# act on multiple mode and reset button presses
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/mode-button.md
-
-:local 0 "mode-button";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global ModeButton;
-
-:global LogPrintExit2;
-
-:set ($ModeButton->"count") ($ModeButton->"count" + 1);
-
-:local Scheduler [ / system scheduler find where name="ModeButtonScheduler" ];
-
-:if ([ :len $Scheduler ] = 0) do={
- $LogPrintExit2 info $0 ("Creating scheduler ModeButtonScheduler, counting presses...") false;
- :global ModeButtonScheduler do={
- :global ModeButton;
-
- :global LogPrintExit2;
- :global ModeButtonScheduler;
- :global ValidateSyntax;
-
- :local LEDInvert do={
- :global ModeButtonLED;
-
- :global IfThenElse;
-
- :local LED [ / system leds find where leds=$ModeButtonLED type~"^(on|off)\$" interface=[] ];
- :if ([ :len $LED ] = 0) do={
- :return false;
- }
- / system leds set type=[ $IfThenElse ([ get $LED type ] = "on") "off" "on" ] $LED;
- }
-
- :local Count ($ModeButton->"count");
- :local Code ($ModeButton->[ :tostr $Count ]);
-
- :set ($ModeButton->"count") 0;
- :set ModeButtonScheduler;
- / system scheduler remove ModeButtonScheduler;
-
- :if ([ :len $Code ] > 0) do={
- :if ([ $ValidateSyntax $Code ] = true) do={
- $LogPrintExit2 info $0 ("Acting on " . $Count . " mode-button presses: " . $Code) false;
-
- :for I from=1 to=$Count do={
- $LEDInvert;
- :if ([ / system routerboard settings get silent-boot ] = false) do={
- :beep length=200ms;
- }
- :delay 200ms;
- $LEDInvert;
- :delay 200ms;
- }
-
- [ :parse $Code ];
- } else={
- $LogPrintExit2 warning $0 ("The code for " . $Count . " mode-button presses failed syntax validation!") false;
- }
- } else={
- $LogPrintExit2 info $0 ("No action defined for " . $Count . " mode-button presses.") false;
- }
- }
- / system scheduler add name="ModeButtonScheduler" \
- on-event=":global ModeButtonScheduler; \$ModeButtonScheduler;" interval=3s;
-} else={
- $LogPrintExit2 debug $0 ("Updating scheduler ModeButtonScheduler...") false;
- / system scheduler set $Scheduler start-time=[ /system clock get time ];
-}
diff --git a/mode-button-event b/mode-button-event
deleted file mode 100644
index 09c0283..0000000
--- a/mode-button-event
+++ /dev/null
@@ -1,6 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: mode-button-event
-
-:global LogPrintExit2;
-
-$LogPrintExit2 warning "mode-button-event" ("This script's functionality has been merged into 'mode-button'.") true;
diff --git a/mode-button-scheduler b/mode-button-scheduler
deleted file mode 100644
index f7045b2..0000000
--- a/mode-button-scheduler
+++ /dev/null
@@ -1,6 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: mode-button-scheduler
-
-:global LogPrintExit2;
-
-$LogPrintExit2 warning "mode-button-scheduler" ("This script's functionality has been merged into 'mode-button'.") true;
diff --git a/mode-button.rsc b/mode-button.rsc
new file mode 100644
index 0000000..f453f11
--- /dev/null
+++ b/mode-button.rsc
@@ -0,0 +1,81 @@
+#!rsc by RouterOS
+# RouterOS script: mode-button
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# act on multiple mode and reset button presses
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/mode-button.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global ModeButton;
+
+ :global LogPrint;
+
+ :set ($ModeButton->"count") ($ModeButton->"count" + 1);
+
+ :local Scheduler [ /system/scheduler/find where name="_ModeButtonScheduler" ];
+
+ :if ([ :len $Scheduler ] = 0) do={
+ $LogPrint info $ScriptName ("Creating scheduler _ModeButtonScheduler, counting presses...");
+ :global ModeButtonScheduler do={
+ :global ModeButton;
+
+ :global LogPrint;
+ :global ModeButtonScheduler;
+ :global ValidateSyntax;
+
+ :local LEDInvert do={
+ :global ModeButtonLED;
+
+ :global IfThenElse;
+
+ :local LED [ /system/leds/find where leds=$ModeButtonLED type~"^(on|off)\$" interface=[] ];
+ :if ([ :len $LED ] = 0) do={
+ :return false;
+ }
+ /system/leds/set type=[ $IfThenElse ([ get $LED type ] = "on") "off" "on" ] $LED;
+ }
+
+ :local Count ($ModeButton->"count");
+ :local Code ($ModeButton->[ :tostr $Count ]);
+
+ :set ($ModeButton->"count") 0;
+ :set ModeButtonScheduler;
+ /system/scheduler/remove [ find where name="_ModeButtonScheduler" ];
+
+ :if ([ :len $Code ] > 0) do={
+ :if ([ $ValidateSyntax $Code ] = true) do={
+ $LogPrint info $ScriptName ("Acting on " . $Count . " mode-button presses: " . $Code);
+
+ :for I from=1 to=$Count do={
+ $LEDInvert;
+ :if ([ /system/routerboard/settings/get silent-boot ] = false) do={
+ :beep length=200ms;
+ }
+ :delay 200ms;
+ $LEDInvert;
+ :delay 200ms;
+ }
+
+ [ :parse $Code ];
+ } else={
+ $LogPrint warning $ScriptName ("The code for " . $Count . " mode-button presses failed syntax validation!");
+ }
+ } else={
+ $LogPrint info $ScriptName ("No action defined for " . $Count . " mode-button presses.");
+ }
+ }
+ /system/scheduler/add name="_ModeButtonScheduler" \
+ on-event=":global ModeButtonScheduler; \$ModeButtonScheduler;" interval=3s;
+ } else={
+ $LogPrint debug $ScriptName ("Updating scheduler _ModeButtonScheduler...");
+ /system/scheduler/set $Scheduler start-time=[ /system/clock/get time ];
+ }
+} on-error={ }
diff --git a/netwatch-dns.rsc b/netwatch-dns.rsc
new file mode 100644
index 0000000..50c2b4c
--- /dev/null
+++ b/netwatch-dns.rsc
@@ -0,0 +1,130 @@
+#!rsc by RouterOS
+# RouterOS script: netwatch-dns
+# Copyright (c) 2022-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# monitor and manage dns/doh with netwatch
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/netwatch-dns.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global CertificateAvailable;
+ :global EitherOr;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :local SettleTime (5m30s - [ /system/resource/get uptime ]);
+ :if ($SettleTime > 0s) do={
+ $LogPrint info $ScriptName ("System just booted, giving netwatch " . $SettleTime . " to settle.");
+ :error true;
+ }
+
+ :local DnsServers ({});
+ :local DnsFallback ({});
+ :local DnsCurrent [ /ip/dns/get servers ];
+
+ :foreach Host in=[ /tool/netwatch/find where comment~"\\bdns\\b" status="up" ] do={
+ :local HostVal [ /tool/netwatch/get $Host ];
+ :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ];
+
+ :if ($HostInfo->"disabled" != true) do={
+ :if ($HostInfo->"dns" = true) do={
+ :set DnsServers ($DnsServers, $HostVal->"host");
+ }
+ :if ($HostInfo->"dns-fallback" = true) do={
+ :set DnsFallback ($DnsFallback, $HostVal->"host");
+ }
+ }
+ }
+
+ :if ([ :len $DnsServers ] > 0) do={
+ :if ($DnsServers != $DnsCurrent) do={
+ $LogPrint info $ScriptName ("Updating DNS servers: " . [ :tostr $DnsServers ]);
+ /ip/dns/set servers=$DnsServers;
+ /ip/dns/cache/flush;
+ }
+ } else={
+ :if ([ :len $DnsFallback ] > 0) do={
+ :if ($DnsFallback != $DnsCurrent) do={
+ $LogPrint info $ScriptName ("Updating DNS servers to fallback: " . [ :tostr $DnsFallback ]);
+ /ip/dns/set servers=$DnsFallback;
+ /ip/dns/cache/flush;
+ }
+ }
+ }
+
+ :local DohCurrent [ /ip/dns/get use-doh-server ];
+ :local DohServers ({});
+
+ :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 or 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);
+ :error true;
+ }
+
+ :set ($DohServers->[ :len $DohServers ]) $HostInfo;
+ }
+ }
+
+ :if ([ :len $DohCurrent ] > 0) do={
+ $LogPrint info $ScriptName ("Current DoH server is down, disabling: " . $DohCurrent);
+ /ip/dns/set use-doh-server="";
+ /ip/dns/cache/flush;
+ }
+
+ :foreach DohServer in=$DohServers do={
+ :if ([ :len ($DohServer->"doh-cert") ] > 0) do={
+ :if ([ $CertificateAvailable ($DohServer->"doh-cert") ] = false) do={
+ $LogPrint warning $ScriptName ("Downloading certificate failed, trying without.");
+ }
+ }
+
+ :local Data false;
+ :do {
+ :set Data ([ /tool/fetch check-certificate=yes-without-crl output=user \
+ http-header-field=({ "accept: application/dns-message" }) \
+ url=(($DohServer->"doh-url") . "?dns=" . [ :convert to=base64 ([ :rndstr length=2 ] . \
+ "\01\00" . "\00\01" . "\00\00" . "\00\00" . "\00\00" . "\09doh-check\05eworm\02de\00" . \
+ "\00\10" . "\00\01") ]) as-value ]->"data");
+ } on-error={
+ $LogPrint warning $ScriptName ("Request to DoH server failed (network or certificate issue): " . \
+ ($DohServer->"doh-url"));
+ }
+
+ :if ($Data != false) do={
+ :if ([ :typeof [ :find $Data "doh-check-OK" ] ] = "num") do={
+ /ip/dns/set use-doh-server=($DohServer->"doh-url") verify-doh-cert=yes;
+ /ip/dns/cache/flush;
+ $LogPrint info $ScriptName ("Setting DoH server: " . ($DohServer->"doh-url"));
+ :error true;
+ } else={
+ $LogPrint warning $ScriptName ("Received unexpected response from DoH server: " . \
+ ($DohServer->"doh-url"));
+ }
+ }
+ }
+} on-error={ }
diff --git a/netwatch-notify b/netwatch-notify
deleted file mode 100644
index ef67804..0000000
--- a/netwatch-notify
+++ /dev/null
@@ -1,97 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: netwatch-notify
-# Copyright (c) 2020-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# monitor netwatch and send notifications
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/netwatch-notify.md
-
-:local 0 "netwatch-notify";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global NetwatchNotify;
-
-:global IfThenElse;
-:global LogPrintExit2;
-:global ParseKeyValueStore;
-:global SendNotification;
-:global SymbolForNotification;
-:global ValidateSyntax;
-
-:if ([ :typeof $NetwatchNotify ] = "nothing") do={
- :set NetwatchNotify [ :toarray "" ];
-}
-
-:foreach Host in=[ / tool netwatch find where comment~"^notify," disabled=no ] do={
- :local HostVal [ / tool netwatch get $Host ];
- :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ];
- :local HostName ($HostInfo->"hostname");
-
- :local Metric { "count"=0; "notified"=false };
- :if ([ :typeof ($NetwatchNotify->$HostName) ] = "array") do={
- :set $Metric ($NetwatchNotify->$HostName);
- }
-
- :if ($HostVal->"status" = "up") do={
- $LogPrintExit2 debug $0 ("Host " . $HostName . " (" . $HostVal->"host" . ") is up.") false;
- :local Count ($Metric->"count");
- :set ($Metric->"count") 0;
- :if ($Metric->"notified" = true) do={
- $SendNotification ([ $SymbolForNotification "white-heavy-check-mark" ] . "Netwatch Notify: " . $HostName . " up") \
- ("Host " . $HostName . " (" . $HostVal->"host" . ") is up since " . $HostVal->"since" . ".\n" . \
- "It was down for " . $Count . " checks since " . ($Metric->"since") . ".");
- :if ([ :typeof ($HostInfo->"up-hook") ] = "str") do={
- :if ([ $ValidateSyntax ($HostInfo->"up-hook") ] = true) do={
- $LogPrintExit2 info $0 ("Running hook on host " . $HostName . " up: " . ($HostInfo->"up-hook")) false;
- [ :parse ($HostInfo->"up-hook") ];
- } else={
- $LogPrintExit2 warning $0 ("The up-hook for host " . $HostName . " failed syntax validation.") false;
- }
- }
- }
- :set ($Metric->"notified") false;
- :set ($Metric->"parent") ($HostInfo->"parent");
- :set ($Metric->"since");
- } else={
- :set ($Metric->"count") ($Metric->"count" + 1);
- :set ($Metric->"parent") ($HostInfo->"parent");
- :set ($Metric->"since") ($HostVal->"since");
- :local Count [ $IfThenElse ([ :tonum ($HostInfo->"count") ] > 0) ($HostInfo->"count") 5 ];
- :local Parent ($HostInfo->"parent");
- :while ([ :len $Parent ] > 0) do={
- :set Count ($Count + 1);
- :set Parent ($NetwatchNotify->$Parent->"parent");
- }
- :set Parent ($HostInfo->"parent");
- :local ParentNotified false;
- :while ($ParentNotified = false && [ :len $Parent ] > 0) do={
- :set ParentNotified [ $IfThenElse (($NetwatchNotify->$Parent->"notified") = true) true false ];
- :if ($ParentNotified = false) do={
- :set Parent ($NetwatchNotify->$Parent->"parent");
- }
- }
- $LogPrintExit2 info $0 ("Host " . $HostName . " (" . $HostVal->"host" . ") is down for " . \
- $Metric->"count" . " checks, " . [ $IfThenElse ($ParentNotified = false) [ $IfThenElse \
- ($Metric->"notified" = true) ("already notified.") ($Count - $Metric->"count" . " to go.") ] \
- ("parent host " . $Parent . " is down.") ]) false;
- :if ($ParentNotified = false && $Metric->"count" >= $Count && $Metric->"notified" != true) do={
- $SendNotification ([ $SymbolForNotification "cross-mark" ] . "Netwatch Notify: " . $HostName . " down") \
- ("Host " . $HostName . " (" . $HostVal->"host" . ") is down since " . $HostVal->"since" . ".");
- :set ($Metric->"notified") true;
- :if ([ :typeof ($HostInfo->"down-hook") ] = "str") do={
- :if ([ $ValidateSyntax ($HostInfo->"down-hook") ] = true) do={
- $LogPrintExit2 info $0 ("Running hook on host " . $HostName . " down: " . ($HostInfo->"down-hook")) false;
- [ :parse ($HostInfo->"down-hook") ];
- } else={
- $LogPrintExit2 warning $0 ("The down-hook for host " . $HostName . " failed syntax validation.") false;
- }
- }
- }
- }
- :set ($NetwatchNotify->$HostName) {
- "count"=($Metric->"count");
- "notified"=($Metric->"notified");
- "parent"=($Metric->"parent");
- "since"=($Metric->"since") };
-}
diff --git a/netwatch-notify.rsc b/netwatch-notify.rsc
new file mode 100644
index 0000000..bdabe2e
--- /dev/null
+++ b/netwatch-notify.rsc
@@ -0,0 +1,219 @@
+#!rsc by RouterOS
+# RouterOS script: netwatch-notify
+# Copyright (c) 2020-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# monitor netwatch and send notifications
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/netwatch-notify.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global NetwatchNotify;
+
+ :global EitherOr;
+ :global IfThenElse;
+ :global IsDNSResolving;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global ScriptFromTerminal;
+ :global ScriptLock;
+ :global SendNotification2;
+ :global SymbolForNotification;
+
+ :local NetwatchNotifyHook do={
+ :local ScriptName [ :tostr $1 ];
+ :local Name [ :tostr $2 ];
+ :local Type [ :tostr $3 ];
+ :local State [ :tostr $4 ];
+ :local Hook [ :tostr $5 ];
+
+ :global LogPrint;
+ :global ValidateSyntax;
+
+ :if ([ $ValidateSyntax $Hook ] = true) do={
+ :do {
+ [ :parse $Hook ];
+ } on-error={
+ $LogPrint warning $ScriptName ("The " . $State . "-hook for " . $Type . " '" . $Name . "' failed to run.");
+ :return ("The hook failed to run.");
+ }
+ } else={
+ $LogPrint warning $ScriptName ("The " . $State . "-hook for " . $Type . " '" . $Name . "' failed syntax validation.");
+ :return ("The hook failed syntax validation.");
+ }
+
+ $LogPrint info $ScriptName ("Ran hook on " . $Type . " '" . $Name . "' " . $State . ": " . $Hook);
+ :return ("Ran hook:\n" . $Hook);
+ }
+
+ :local ResolveExpected do={
+ :local ScriptName [ :tostr $1 ];
+ :local Name [ :tostr $2 ];
+ :local Expected [ :tostr $3 ];
+
+ :global GetRandom20CharAlNum;
+
+ :local FwAddrList ($ScriptName . "-" . [ $GetRandom20CharAlNum ]);
+ /ip/firewall/address-list/add address=$Name list=$FwAddrList dynamic=yes timeout=1s;
+ :delay 20ms;
+ :if ([ :len [ /ip/firewall/address-list/find where list=$FwAddrList address=$Expected ] ] > 0) do={
+ :return true;
+ }
+ /ipv6/firewall/address-list/add address=$Name list=$FwAddrList dynamic=yes timeout=1s;
+ :delay 20ms;
+ :if ([ :len [ /ipv6/firewall/address-list/find where list=$FwAddrList address=$Expected ] ] > 0) do={
+ :return true;
+ }
+
+ :return false;
+ }
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :local ScriptFromTerminalCached [ $ScriptFromTerminal $ScriptName ];
+
+ :if ([ :typeof $NetwatchNotify ] = "nothing") do={
+ :set NetwatchNotify ({});
+ }
+
+ :foreach Host in=[ /tool/netwatch/find where comment~"\\bnotify\\b" !disabled status!="unknown" ] do={
+ :local HostVal [ /tool/netwatch/get $Host ];
+ :local Type [ $IfThenElse ($HostVal->"type" ~ "^(https?-get|tcp-conn)\$") "service" "host" ];
+ :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ];
+ :local HostDetails ($HostVal->"host" . \
+ [ $IfThenElse ([ :len ($HostInfo->"resolve") ] > 0) (", " . $HostInfo->"resolve") ]);
+
+ :if ($HostInfo->"notify" = true && $HostInfo->"disabled" != true) do={
+ :local Name [ $EitherOr ($HostInfo->"name") ($HostVal->"name") ];
+
+ :local Metric { "count-down"=0; "count-up"=0; "notified"=false; "resolve-failcnt"=0 };
+ :if ([ :typeof ($NetwatchNotify->$Name) ] = "array") do={
+ :set $Metric ($NetwatchNotify->$Name);
+ }
+
+ :if ([ :typeof ($HostInfo->"resolve") ] = "str") do={
+ :if ([ $IsDNSResolving ] = true) do={
+ :do {
+ :local Resolve [ :resolve ($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 warning $ScriptName ("Resolving name '" . $HostInfo->"resolve" . [ $IfThenElse \
+ ($HostInfo->"resolve" != $HostInfo->"name") ("' for " . $Type . " '" . \
+ $HostInfo->"name") "" ] . "' failed.");
+ }
+ }
+ }
+ }
+
+ :if ($HostVal->"status" = "up") do={
+ :local CountDown ($Metric->"count-down");
+ :if ($CountDown > 0) do={
+ $LogPrint info $ScriptName \
+ ("The " . $Type . " '" . $Name . "' (" . $HostDetails . ") is up.");
+ :set ($Metric->"count-down") 0;
+ }
+ :set ($Metric->"count-up") ($Metric->"count-up" + 1);
+ :if ($Metric->"notified" = true) do={
+ :local Message ("The " . $Type . " '" . $Name . "' (" . $HostDetails . \
+ ") is up since " . $HostVal->"since" . ".\n" . \
+ "It was down for " . $CountDown . " checks since " . ($Metric->"since") . ".");
+ :if ([ :typeof ($HostInfo->"note") ] = "str") do={
+ :set Message ($Message . "\n\nNote:\n" . ($HostInfo->"note"));
+ }
+ :if ([ :typeof ($HostInfo->"up-hook") ] = "str") do={
+ :set Message ($Message . "\n\n" . [ $NetwatchNotifyHook $ScriptName $Name $Type "up" \
+ ($HostInfo->"up-hook") ]);
+ }
+ $SendNotification2 ({ origin=[ $EitherOr ($HostInfo->"origin") $ScriptName ]; silent=($HostInfo->"silent"); \
+ subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Netwatch Notify: " . $Name . " up"); \
+ message=$Message; link=($HostInfo->"link") });
+ }
+ :set ($Metric->"notified") false;
+ :set ($Metric->"parent") ($HostInfo->"parent");
+ :set ($Metric->"since");
+ }
+
+ :if ($HostVal->"status" = "down") do={
+ :set ($Metric->"count-down") ($Metric->"count-down" + 1);
+ :set ($Metric->"count-up") 0;
+ :set ($Metric->"parent") ($HostInfo->"parent");
+ :set ($Metric->"since") ($HostVal->"since");
+ :local CountDown [ $IfThenElse ([ :tonum ($HostInfo->"count") ] > 0) ($HostInfo->"count") 5 ];
+ :local Parent ($HostInfo->"parent");
+ :local ParentUp false;
+ :while ([ :len $Parent ] > 0) do={
+ :set CountDown ($CountDown + 1);
+ :set Parent ($NetwatchNotify->$Parent->"parent");
+ }
+ :set Parent ($HostInfo->"parent");
+ :local ParentNotified false;
+ :while ($ParentNotified = false && [ :len $Parent ] > 0) do={
+ :set ParentNotified [ $IfThenElse (($NetwatchNotify->$Parent->"notified") = true) \
+ true false ];
+ :set ParentUp ($NetwatchNotify->$Parent->"count-up");
+ :if ($ParentNotified = false) do={
+ :set Parent ($NetwatchNotify->$Parent->"parent");
+ }
+ }
+ :if ($Metric->"notified" = false || $Metric->"count-down" % 120 = 0 || \
+ $ScriptFromTerminalCached = true) do={
+ $LogPrint [ $IfThenElse ($HostInfo->"no-down-notification" != true) info debug ] $ScriptName \
+ ("The " . $Type . " '" . $Name . "' (" . $HostDetails . ") is down for " . \
+ $Metric->"count-down" . " checks, " . [ $IfThenElse ($ParentNotified = false) [ $IfThenElse \
+ ($Metric->"notified" = true) ("already notified.") ($CountDown - $Metric->"count-down" . \
+ " to go.") ] ("parent " . $Type . " " . $Parent . " is down.") ]);
+ }
+ :if ((($CountDown * 2) - ($Metric->"count-down" * 3)) / 2 = 0 && \
+ [ :typeof ($HostInfo->"pre-down-hook") ] = "str") do={
+ $NetwatchNotifyHook $ScriptName $Name $Type "pre-down" ($HostInfo->"pre-down-hook");
+ }
+ :if ($ParentNotified = false && $Metric->"count-down" >= $CountDown && \
+ ($ParentUp = false || $ParentUp > 2) && $Metric->"notified" != true) do={
+ :local Message ("The " . $Type . " '" . $Name . "' (" . $HostDetails . \
+ ") is down since " . $HostVal->"since" . ".");
+ :if ([ :typeof ($HostInfo->"note") ] = "str") do={
+ :set Message ($Message . "\n\nNote:\n" . ($HostInfo->"note"));
+ }
+ :if ([ :typeof ($HostInfo->"down-hook") ] = "str") do={
+ :set Message ($Message . "\n\n" . [ $NetwatchNotifyHook $ScriptName $Name $Type "down" \
+ ($HostInfo->"down-hook") ]);
+ }
+ :if ($HostInfo->"no-down-notification" != true) do={
+ $SendNotification2 ({ origin=[ $EitherOr ($HostInfo->"origin") $ScriptName ]; silent=($HostInfo->"silent"); \
+ subject=([ $SymbolForNotification "cross-mark" ] . "Netwatch Notify: " . $Name . " down"); \
+ message=$Message; link=($HostInfo->"link") });
+ }
+ :set ($Metric->"notified") true;
+ }
+ }
+
+ :set ($NetwatchNotify->$Name) {
+ "count-down"=($Metric->"count-down");
+ "count-up"=($Metric->"count-up");
+ "notified"=($Metric->"notified");
+ "parent"=($Metric->"parent");
+ "resolve-failcnt"=($Metric->"resolve-failcnt");
+ "since"=($Metric->"since") };
+ }
+ }
+} on-error={ }
diff --git a/netwatch-syslog b/netwatch-syslog
deleted file mode 100644
index f274c18..0000000
--- a/netwatch-syslog
+++ /dev/null
@@ -1,17 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: netwatch-syslog
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# requires: dont-require-permissions=yes
-#
-# manage remote logging facilities
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/netwatch-syslog.md
-
-:local Remote [ /system logging action get ([ find where target=remote ]->0) remote ];
-
-if ([ / tool netwatch get [ find where host=$Remote up-script="netwatch-syslog" down-script="netwatch-syslog" ] status ] = "up") do={
- / system logging set disabled=no [ find where action=remote disabled=yes ];
-} else={
- / system logging set disabled=yes [ find where action=remote disabled=no ];
-}
diff --git a/news-and-changes.rsc b/news-and-changes.rsc
new file mode 100644
index 0000000..1f1094d
--- /dev/null
+++ b/news-and-changes.rsc
@@ -0,0 +1,60 @@
+# News, changes and migration by RouterOS Scripts
+# Copyright (c) 2019-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+
+:global IDonate;
+
+:global IfThenElse;
+:global RequiredRouterOS;
+:global SymbolForNotification;
+
+:local Resource [ /system/resource/get ];
+
+# News, changes and migration up to change 95:
+# https://git.eworm.de/cgit/routeros-scripts/plain/global-config.changes?h=change-95
+
+# Changes for global-config to be added to notification on script updates
+:global GlobalConfigChanges {
+ 96="Added support for notes in 'netwatch-notify', these are included verbatim into the notification.";
+ 97="Modified 'dhcp-to-dns' to always add A records for names with mac address, and optionally add CNAME records if the host name is available.";
+ 98="Extended 'check-certificates' to download new certificate by SubjectAltNames if download by CommonName fails.";
+ 99="Modified 'dhcp-to-dns', which dropped global configuration. Settings moved to dhcp server's network definitions.";
+ 100="The script 'ssh-keys-import' became a module 'mod/ssh-keys-import' with enhanced functionality.";
+ 101="Introduced new script 'fw-addr-lists' to download, import and update firewall address-lists.";
+ 102="Modified 'hotspot-to-wpa' to support non-local (radius) users.";
+ 103="Dropped hard-coded name and timeout from 'hotspot-to-wpa-cleanup', instead a comment is required for dhcp server now.";
+ 104="All relevant scripts were ported to new wifiwave2 and are available for AX devices now!";
+ 105="Extended 'check-routeros-update' to support automatic update from specific neighbor(s).";
+ 106="Modified 'telegram-chat' to make it act on message replies, without activation. Also made it answer a single question mark with a short notice.";
+ 107="Dropped support for non-fixed width font in Telegram notifications.";
+ 108="Enhanced 'log-forward' to list log messages with colorful bullets to indicate severity.";
+ 109="Added support to send notifications via Ntfy (ntfy.sh).";
+ 110="Dropped support for loading scripts from local storage.";
+ 111="Modified 'dhcp-to-dns' to allow multiple records for one mac address.";
+ 112="Enhanced 'mod/ssh-keys-import' to record the fingerprint of keys.";
+ 113="Added helper functions for easier setup to Matrix notification module.";
+ 114="All relevant scripts were ported to new wifi package for RouterOS 7.13 and later. Migration is complex and thus not done automatically!";
+ 115=("Celebrating " . [ $SymbolForNotification "sparkles,star" ] . "1.000 stars " . [ $SymbolForNotification "star,sparkles" ] . "on Github! Please continue starring...");
+ 116=("... and also please keep in mind that it takes a huge amount of time maintaining these scripts. " . [ $IfThenElse ($IDonate != true) \
+ ("Following the donation hint " . [ $SymbolForNotification "arrow-down" "below" ] . "to keep me motivated is much appreciated. Thanks!") \
+ ("Looks like you did donate already. " . [ $SymbolForNotification "heart" "<3" ] . "Much appreciated, thanks!") ]);
+ 117="Enhanced 'packages-update' to support deferred reboot on automatically installed updates.";
+ 118=("RouterOS packages increase in size with each release. This becomes a problem for devices with 16MB storage and below. " . \
+ [ $IfThenElse ($Resource->"total-hdd-space" < 16000000) ("Your " . $Resource->"board-name" . " is specifically affected! ") \
+ [ $IfThenElse ($Resource->"free-hdd-space" > 4000000) ("(Your " . $Resource->"board-name" . " does not suffer this issue.) ") ] ] . \
+ "Huge configuration and lots of scripts give an extra risk. Take care!");
+ 119="Added support for IPv6 to script 'fw-addr-lists'.";
+ 120="Implemented a workaround in 'backup-cloud'. Now script should no longer just crash, but send notification with error.";
+ 121="The 'wifiwave2' scripts are finally gone. Development continues with 'wifi' in RouterOS 7.13 and later.";
+ 122="The global configuration was enhanced to support loading snippets. Configuration can be split off to scripts where name starts with 'global-config-overlay.d/'.";
+ 123="Introduced new function '\$LogPrint', and deprecated '\$LogPrintExit2'. Please update custom scripts if you use it.";
+ 124="Added support for links in 'netwatch-notify', these are added below the formatted notification text.";
+};
+
+# 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; }";
+ 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; }";
+};
diff --git a/ospf-to-leds b/ospf-to-leds
deleted file mode 100644
index 727f33b..0000000
--- a/ospf-to-leds
+++ /dev/null
@@ -1,28 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: ospf-to-leds
-# Copyright (c) 2020-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# visualize ospf instance state via leds
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/ospf-to-leds.md
-
-:local 0 "ospf-to-leds";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global LogPrintExit2;
-:global ParseKeyValueStore;
-
-:foreach Instance in=[ / routing ospf instance find where comment~"^ospf-to-leds," ] do={
- :local InstanceVal [ / routing ospf instance get $Instance ];
- :local LED ([ $ParseKeyValueStore ($InstanceVal->"comment") ]->"leds");
- :local LEDType [ / system leds get [ find where leds=$LED ] type ];
-
- $LogPrintExit2 debug $0 ("OSPF instance " . $InstanceVal->"name" . " is " . $InstanceVal->"state" . ".") false;
- :if ($InstanceVal->"state" = "running" && $LEDType = "off") do={
- / system leds set type=on [ find where leds=$LED ];
- }
- :if ($InstanceVal->"state" = "down" && $LEDType = "on") do={
- / system leds set type=off [ find where leds=$LED ];
- }
-}
diff --git a/ospf-to-leds.rsc b/ospf-to-leds.rsc
new file mode 100644
index 0000000..a22e5a5
--- /dev/null
+++ b/ospf-to-leds.rsc
@@ -0,0 +1,45 @@
+#!rsc by RouterOS
+# RouterOS script: ospf-to-leds
+# Copyright (c) 2020-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# visualize ospf instance state via leds
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/ospf-to-leds.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :foreach Instance in=[ /routing/ospf/instance/find where comment~"^ospf-to-leds," ] do={
+ :local InstanceVal [ /routing/ospf/instance/get $Instance ];
+ :local LED ([ $ParseKeyValueStore ($InstanceVal->"comment") ]->"leds");
+ :local LEDType [ /system/leds/get [ find where leds=$LED ] type ];
+
+ :local NeighborCount 0;
+ :foreach Area in=[ /routing/ospf/area/find where instance=($InstanceVal->"name") ] do={
+ :local AreaName [ /routing/ospf/area/get $Area name ];
+ :set NeighborCount ($NeighborCount + [ :len [ /routing/ospf/neighbor/find where area=$AreaName ] ]);
+ }
+
+ :if ($NeighborCount > 0 && $LEDType = "off") do={
+ $LogPrint info $ScriptName ("OSPF instance " . $InstanceVal->"name" . " has " . $NeighborCount . " neighbors, led on!");
+ /system/leds/set type=on [ find where leds=$LED ];
+ }
+ :if ($NeighborCount = 0 && $LEDType = "on") do={
+ $LogPrint info $ScriptName ("OSPF instance " . $InstanceVal->"name" . " has no neighbors, led off!");
+ /system/leds/set type=off [ find where leds=$LED ];
+ }
+ }
+} on-error={ }
diff --git a/packages-update b/packages-update
deleted file mode 100644
index 99f1f30..0000000
--- a/packages-update
+++ /dev/null
@@ -1,93 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: packages-update
-# Copyright (c) 2019-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# download packages and reboot for installation
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/packages-update.md
-
-:local 0 "packages-update";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global DownloadPackage;
-:global LogPrintExit2;
-:global ScriptFromTerminal;
-:global ScriptLock;
-:global VersionToNum;
-
-$ScriptLock $0;
-
-:local Update [ / system package update get ];
-
-:if ([ :typeof ($Update->"latest-version") ] = "nothing") do={
- $LogPrintExit2 warning $0 ("Latest version is not known.") true;
-}
-
-:if ($Update->"installed-version" = $Update->"latest-version") do={
- $LogPrintExit2 info $0 ("Version " . $Update->"latest-version" . " is already installed.") true;
-}
-
-:local NumInstalled [ $VersionToNum ($Update->"installed-version") ];
-:local NumLatest [ $VersionToNum ($Update->"latest-version") ];
-
-:local DoDowngrade false;
-:if ($NumInstalled > $NumLatest) do={
- :if ([ $ScriptFromTerminal $0 ] = true) do={
- :put "Latest version is older than installed one. Want to downgrade? [y/N]";
- :if (([ :terminal inkey timeout=60 ] % 32) = 25) do={
- :set DoDowngrade true;
- } else={
- :put "Canceled...";
- }
- } else={
- $LogPrintExit2 warning $0 ("Not installing downgrade automatically.") true;
- }
-}
-
-:foreach Package in=[ / system package find where !bundle ] do={
- :local PkgName [ / system package get $Package name ];
- if ([ $DownloadPackage $PkgName ($Update->"latest-version") ] = false) do={
- $LogPrintExit2 error $0 ("Download for package " . $PkgName . " failed.") true;
- }
-}
-
-:foreach Script in=[ / system script find where name~"^(cloud|email|upload)-backup\$" ] do={
- :local ScriptName [ / system script get $Script name ];
- :do {
- $LogPrintExit2 info $0 ("Running backup script " . $ScriptName . " before update.") false;
- / system script run $Script;
- } on-error={
- $LogPrintExit2 warning $0 ("Running backup script " . $ScriptName . " before update failed!") false;
- :if ([ $ScriptFromTerminal $0 ] = true) do={
- :put "Do you want to continue anyway? [y/N]";
- :if (([ :terminal inkey timeout=60 ] % 32) = 25) do={
- $LogPrintExit2 info $0 ("User requested to continue anyway.") false;
- } else={
- $LogPrintExit2 info $0 ("Canceled update...") true;
- }
- } else={
- $LogPrintExit2 info $0 ("Canceled non-interactive update.") true;
- }
- }
-}
-
-:if ($DoDowngrade = true) do={
- $LogPrintExit2 info $0 ("Rebooting for downgrade.") false;
- :delay 1s;
- / system package downgrade;
-}
-
-:if ([ $ScriptFromTerminal $0 ] = true) do={
- :put "Do you want to (s)chedule reboot or (r)eboot now? [s/R]";
- :if (([ :terminal inkey timeout=60 ] % 32) = 19) do={
- / system scheduler add name="reboot-for-update" start-time=03:00:00 interval=1d \
- on-event=(":global RandomDelay; \$RandomDelay 3600; " . \
- "/ system scheduler remove reboot-for-update; / system reboot;");
- $LogPrintExit2 info $0 ("Scheduled reboot for update between 03:00 and 04:00.") true;
- }
-}
-
-$LogPrintExit2 info $0 ("Rebooting for update.") false;
-:delay 1s;
-/ system reboot;
diff --git a/packages-update.rsc b/packages-update.rsc
new file mode 100644
index 0000000..0208b1e
--- /dev/null
+++ b/packages-update.rsc
@@ -0,0 +1,145 @@
+#!rsc by RouterOS
+# RouterOS script: packages-update
+# Copyright (c) 2019-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.13
+#
+# download packages and reboot for installation
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/packages-update.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global DownloadPackage;
+ :global Grep;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global ScriptFromTerminal;
+ :global ScriptLock;
+ :global VersionToNum;
+
+ :global PackagesUpdateDeferReboot;
+ :global PackagesUpdateBackupFailure;
+
+ :local Schedule do={
+ :local ScriptName [ :tostr $1 ];
+
+ :global GetRandomNumber;
+ :global LogPrint;
+
+ :global RebootForUpdate do={
+ /system/reboot;
+ }
+
+ :local StartTime [ :tostr [ :totime (10800 + [ $GetRandomNumber 7200 ]) ] ];
+ /system/scheduler/add name="_RebootForUpdate" start-time=$StartTime interval=1d \
+ on-event=("/system/scheduler/remove \"_RebootForUpdate\"; " . \
+ ":global RebootForUpdate; \$RebootForUpdate;");
+ $LogPrint info $ScriptName ("Scheduled reboot for update at " . $StartTime . \
+ " local time (" . [ /system/clock/get time-zone-name ] . ").");
+ :return true;
+ }
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :local Update [ /system/package/update/get ];
+
+ :if ([ :typeof ($Update->"latest-version") ] = "nothing") do={
+ $LogPrint warning $ScriptName ("Latest version is not known.");
+ :error false;
+ }
+
+ :if ($Update->"installed-version" = $Update->"latest-version") do={
+ $LogPrint info $ScriptName ("Version " . $Update->"latest-version" . " is already installed.");
+ :error true;
+ }
+
+ :local NumInstalled [ $VersionToNum ($Update->"installed-version") ];
+ :local NumLatest [ $VersionToNum ($Update->"latest-version") ];
+
+ :local DoDowngrade false;
+ :if ($NumInstalled > $NumLatest) do={
+ :if ([ $ScriptFromTerminal $ScriptName ] = true) do={
+ :put "Latest version is older than installed one. Want to downgrade? [y/N]";
+ :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={
+ :set DoDowngrade true;
+ } else={
+ :put "Canceled...";
+ }
+ } else={
+ $LogPrint warning $ScriptName ("Not installing downgrade automatically.");
+ :error false;
+ }
+ }
+
+ :foreach Package in=[ /system/package/find where !bundle ] 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.");
+ :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, ") ] ];
+
+ :set ($RunOrder->($Store->"order" . "-" . $ScriptVal->"name")) ($ScriptVal->"name");
+ }
+
+ :foreach Order,Script in=$RunOrder do={
+ :set PackagesUpdateBackupFailure false;
+ :do {
+ $LogPrint info $ScriptName ("Running backup script " . $Script . " before update.");
+ /system/script/run $Script;
+ } on-error={
+ :set PackagesUpdateBackupFailure true;
+ }
+
+ :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...");
+ :error false;
+ }
+ } else={
+ $LogPrint warning $ScriptName ("Canceled non-interactive update.");
+ :error false;
+ }
+ }
+ }
+
+ :if ($DoDowngrade = true) do={
+ $LogPrint info $ScriptName ("Rebooting for downgrade.");
+ :delay 1s;
+ /system/package/downgrade;
+ }
+
+ :if ([ $ScriptFromTerminal $ScriptName ] = true) do={
+ :put "Do you want to (s)chedule reboot or (r)eboot now? [s/R]";
+ :if (([ /terminal/inkey timeout=60 ] % 32) = 19) do={
+ $Schedule $ScriptName;
+ :error true;
+ }
+ } else={
+ :if ($PackagesUpdateDeferReboot = true) do={
+ $Schedule $ScriptName;
+ :error true;
+ }
+ }
+
+ $LogPrint info $ScriptName ("Rebooting for update.");
+ :delay 1s;
+ /system/reboot;
+} on-error={ }
diff --git a/ppp-on-up b/ppp-on-up
deleted file mode 100644
index fe1492d..0000000
--- a/ppp-on-up
+++ /dev/null
@@ -1,35 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: ppp-on-up
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# run scripts on ppp up
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/ppp-on-up.md
-
-:local 0 "ppp-on-up";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global LogPrintExit2;
-
-:local Interface $interface;
-
-:if ([ :typeof $Interface ] = "nothing") do={
- $LogPrintExit2 error $0 ("This script is supposed to run from ppp on-up script hook.") true;
-}
-
-:local IntName [ / interface get $Interface name ];
-:log info ("PPP interface " . $IntName . " is up.");
-
-/ ipv6 dhcp-client release [ find where interface=$IntName !disabled ];
-
-:local Scripts {
- "update-tunnelbroker"
-}
-
-:foreach Script in=$Scripts do={
- :if ([ :len [ / system script find where name=$Script ] ] > 0) do={
- :log debug ("Running script from ppp-on-up: " . $Script);
- / system script run $Script;
- }
-}
diff --git a/ppp-on-up.rsc b/ppp-on-up.rsc
new file mode 100644
index 0000000..61766c0
--- /dev/null
+++ b/ppp-on-up.rsc
@@ -0,0 +1,40 @@
+#!rsc by RouterOS
+# RouterOS script: ppp-on-up
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# run scripts on ppp up
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/ppp-on-up.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global LogPrint;
+
+ :local Interface $interface;
+
+ :if ([ :typeof $Interface ] = "nothing") do={
+ $LogPrint error $ScriptName ("This script is supposed to run from ppp on-up script hook.");
+ :error false;
+ }
+
+ :local IntName [ /interface/get $Interface name ];
+ $LogPrint info $ScriptName ("PPP interface " . $IntName . " is up.");
+
+ /ipv6/dhcp-client/release [ find where interface=$IntName !disabled ];
+
+ :foreach Script in=[ /system/script/find where source~("\n# provides: ppp-on-up\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={ }
diff --git a/rotate-ntp b/rotate-ntp
deleted file mode 100644
index 24eb107..0000000
--- a/rotate-ntp
+++ /dev/null
@@ -1,32 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: rotate-ntp
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# rotate the ntp servers
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/rotate-ntp.md
-
-:local 0 "rotate-ntp";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global NtpPool;
-
-:global LogPrintExit2;
-
-:local Ntp1;
-:local Ntp2;
-
-:if ([ / system ntp client get enabled ] != true) do={
- $LogPrintExit2 warning $0 ("NTP client is not enabled!") true;
-}
-
-:do {
- :set Ntp1 [ :resolve ("0." . $NtpPool) ];
- :set Ntp2 [ :resolve ("1." . $NtpPool) ];
-} on-error={
- $LogPrintExit2 warning $0 ("Resolving NTP server failed.") true;
-}
-
-$LogPrintExit2 info $0 ("Updating NTP servers to " . $Ntp1 . " and " . $Ntp2) false;
-/ system ntp client set primary-ntp=$Ntp1 secondary-ntp=$Ntp2;
diff --git a/script-updates b/script-updates
deleted file mode 100644
index e35f15c..0000000
--- a/script-updates
+++ /dev/null
@@ -1,6 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: script-updates
-
-:global LogPrintExit2;
-
-$LogPrintExit2 warning "script-updates" ("This script has been replaced by function '\$ScriptInstallUpdate'.") true;
diff --git a/sms-action b/sms-action
deleted file mode 100644
index e48c632..0000000
--- a/sms-action
+++ /dev/null
@@ -1,31 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: sms-action
-# Copyright (c) 2018-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# run action on received SMS
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/sms-action.md
-
-:local 0 "sms-action";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global SmsAction;
-
-:global LogPrintExit2;
-:global ValidateSyntax;
-
-:local Action $action;
-
-:if ([ :typeof $Action ] = "nothing") do={
- $LogPrintExit2 error $0 ("This script is supposed to run from SMS hook with action=...") true;
-}
-
-:local Code ($SmsAction->$Action);
-:if ([ $ValidateSyntax $Code ] = true) do={
- :log info ("Acting on SMS action '" . $Action . "': " . $Code);
- :delay 1s;
- [ :parse $Code ];
-} else={
- $LogPrintExit2 warning $0 ("The code for action '" . $Action . "' failed syntax validation!") false;
-}
diff --git a/sms-action.rsc b/sms-action.rsc
new file mode 100644
index 0000000..4c37565
--- /dev/null
+++ b/sms-action.rsc
@@ -0,0 +1,37 @@
+#!rsc by RouterOS
+# RouterOS script: sms-action
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# run action on received SMS
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/sms-action.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global SmsAction;
+
+ :global LogPrint;
+ :global ValidateSyntax;
+
+ :local Action $action;
+
+ :if ([ :typeof $Action ] = "nothing") do={
+ $LogPrint error $ScriptName ("This script is supposed to run from SMS hook with action=...");
+ :error false;
+ }
+
+ :local Code ($SmsAction->$Action);
+ :if ([ $ValidateSyntax $Code ] = true) do={
+ :log info ("Acting on SMS action '" . $Action . "': " . $Code);
+ :delay 1s;
+ [ :parse $Code ];
+ } else={
+ $LogPrint warning $ScriptName ("The code for action '" . $Action . "' failed syntax validation!");
+ }
+} on-error={ }
diff --git a/sms-forward b/sms-forward
deleted file mode 100644
index 1d47000..0000000
--- a/sms-forward
+++ /dev/null
@@ -1,61 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: sms-forward
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# forward SMS to e-mail
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/sms-forward.md
-
-:local 0 "sms-forward";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global Identity;
-
-:global IfThenElse;
-:global LogPrintExit2;
-:global ScriptLock;
-:global SendNotification;
-:global SymbolForNotification;
-:global WaitFullyConnected;
-
-$ScriptLock $0;
-
-:if ([ / tool sms get receive-enabled ] = false) do={
- $LogPrintExit2 warning $0 ("Receiving of SMS is not enabled.") true;
-}
-
-$WaitFullyConnected;
-
-:local Settings [ / tool sms get ];
-
-# forward SMS in a loop
-:while ([ :len [ / tool sms inbox find ] ] > 0) do={
- :local Phone [ / tool sms inbox get ([ find ]->0) phone ];
- :local Messages "";
- :local Delete [ :toarray "" ];
-
- :foreach Sms in=[ / tool sms inbox find where phone=$Phone ] do={
- :local SmsVal [ / tool sms inbox get $Sms ];
-
- :if ($Phone = $Settings->"allowed-number" && \
- ($SmsVal->"message")~("^:cmd " . $Settings->"secret" . " script ")) do={
- $LogPrintExit2 debug $0 ("Removing SMS, which started a script.") false;
- / tool sms inbox remove $Sms;
- } else={
- :set Messages ($Messages . "\n\nOn " . $SmsVal->"timestamp" . \
- " type " . $SmsVal->"type" . ":\n" . $SmsVal->"message");
- :set Delete ($Delete, $Sms);
- }
- }
-
- :if ([ :len $Messages ] > 0) do={
- :local Count [ :len $Delete ];
- $SendNotification ([ $SymbolForNotification "incoming-envelope" ] . "SMS Forwarding from " . $Phone) \
- ("Received " . [ $IfThenElse ($Count = 1) "this message" ("these " . $Count . " messages") ] . \
- " by " . $Identity . " from " . $Phone . ":" . $Messages);
- :foreach Sms in=$Delete do={
- / tool sms inbox remove $Sms;
- }
- }
-}
diff --git a/sms-forward.rsc b/sms-forward.rsc
new file mode 100644
index 0000000..b0966c3
--- /dev/null
+++ b/sms-forward.rsc
@@ -0,0 +1,95 @@
+#!rsc by RouterOS
+# RouterOS script: sms-forward
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# Anatoly Bubenkov <bubenkoff@gmail.com>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# forward SMS to e-mail
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/sms-forward.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global Identity;
+ :global SmsForwardHooks;
+
+ :global IfThenElse;
+ :global LogPrint;
+ :global LogPrintOnce;
+ :global ScriptLock;
+ :global SendNotification2;
+ :global SymbolForNotification;
+ :global ValidateSyntax;
+ :global WaitFullyConnected;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :if ([ /tool/sms/get receive-enabled ] = false) do={
+ $LogPrintOnce warning $ScriptName ("Receiving of SMS is not enabled.");
+ :error false;
+ }
+
+ $WaitFullyConnected;
+
+ :local Settings [ /tool/sms/get ];
+
+ :if ([ /interface/lte/get ($Settings->"port") running ] != true) do={
+ $LogPrint info $ScriptName ("The LTE interface is not in running state, skipping.");
+ :error true;
+ }
+
+ # forward SMS in a loop
+ :while ([ :len [ /tool/sms/inbox/find ] ] > 0) do={
+ :local Phone [ /tool/sms/inbox/get ([ find ]->0) phone ];
+ :local Messages "";
+ :local Delete ({});
+
+ :foreach Sms in=[ /tool/sms/inbox/find where phone=$Phone ] do={
+ :local SmsVal [ /tool/sms/inbox/get $Sms ];
+
+ :if ($Phone = $Settings->"allowed-number" && \
+ ($SmsVal->"message")~("^:cmd " . $Settings->"secret" . " script ")) do={
+ $LogPrint debug $ScriptName ("Removing SMS, which started a script.");
+ /tool/sms/inbox/remove $Sms;
+ } else={
+ :set Messages ($Messages . "\n\nOn " . $SmsVal->"timestamp" . \
+ " type " . $SmsVal->"type" . ":\n" . $SmsVal->"message");
+ :foreach Hook in=$SmsForwardHooks do={
+ :if ($Phone~($Hook->"allowed-number") && ($SmsVal->"message")~($Hook->"match")) do={
+ :if ([ $ValidateSyntax ($Hook->"command") ] = true) do={
+ $LogPrint info $ScriptName ("Running hook '" . $Hook->"match" . "': " . $Hook->"command");
+ :do {
+ :local Command [ :parse ($Hook->"command") ];
+ $Command Phone=$Phone Message=($SmsVal->"message");
+ :set Messages ($Messages . "\n\nRan hook '" . $Hook->"match" . "':\n" . $Hook->"command");
+ } on-error={
+ $LogPrint warning $ScriptName ("The code for hook '" . $Hook->"match" . "' failed to run!");
+ }
+ } else={
+ $LogPrint warning $ScriptName ("The code for hook '" . $Hook->"match" . "' failed syntax validation!");
+ }
+ }
+ }
+ :set Delete ($Delete, $Sms);
+ }
+ }
+
+ :if ([ :len $Messages ] > 0) do={
+ :local Count [ :len $Delete ];
+ $SendNotification2 ({ origin=$ScriptName; \
+ subject=([ $SymbolForNotification "incoming-envelope" ] . "SMS Forwarding from " . $Phone); \
+ message=("Received " . [ $IfThenElse ($Count = 1) "this message" ("these " . $Count . " messages") ] . \
+ " by " . $Identity . " from " . $Phone . ":" . $Messages) });
+ :foreach Sms in=$Delete do={
+ /tool/sms/inbox/remove $Sms;
+ }
+ }
+ }
+} on-error={ }
diff --git a/ssh-keys-import b/ssh-keys-import
deleted file mode 100644
index 1f158fd..0000000
--- a/ssh-keys-import
+++ /dev/null
@@ -1,11 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: ssh-keys-import
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# import ssh keys from file
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/ssh-keys-import.md
-
-:foreach Key in=[ / file find where type="ssh key" ] do={
- / user ssh-key import user=admin public-key-file=[ / file get $Key name ];
-}
diff --git a/super-mario-theme b/super-mario-theme.rsc
index e13c934..63308b0 100644
--- a/super-mario-theme
+++ b/super-mario-theme.rsc
@@ -1,6 +1,6 @@
#!rsc by RouterOS
# RouterOS script: super-mario-theme
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
#
# play Super Mario theme
diff --git a/telegram-chat.rsc b/telegram-chat.rsc
new file mode 100644
index 0000000..9ae5967
--- /dev/null
+++ b/telegram-chat.rsc
@@ -0,0 +1,180 @@
+#!rsc by RouterOS
+# RouterOS script: telegram-chat
+# Copyright (c) 2023-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# use Telegram to chat with your Router and send commands
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/telegram-chat.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global Identity;
+ :global TelegramChatActive;
+ :global TelegramChatGroups;
+ :global TelegramChatId;
+ :global TelegramChatIdsTrusted;
+ :global TelegramChatOffset;
+ :global TelegramChatRunTime;
+ :global TelegramMessageIDs;
+ :global TelegramRandomDelay;
+ :global TelegramTokenId;
+
+ :global CertificateAvailable;
+ :global EitherOr;
+ :global EscapeForRegEx;
+ :global GetRandom20CharAlNum;
+ :global IfThenElse;
+ :global LogPrint;
+ :global MAX;
+ :global MIN;
+ :global MkDir;
+ :global ParseJson;
+ :global RandomDelay;
+ :global ScriptLock;
+ :global SendTelegram2;
+ :global SymbolForNotification;
+ :global ValidateSyntax;
+ :global WaitForFile;
+ :global WaitFullyConnected;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ $WaitFullyConnected;
+
+ :if ([ :typeof $TelegramChatOffset ] != "array") do={
+ :set TelegramChatOffset { 0; 0; 0 };
+ }
+ :if ([ :typeof $TelegramRandomDelay ] != "num") do={
+ :set TelegramRandomDelay 0;
+ }
+
+ :if ([ $CertificateAvailable "Go Daddy Secure Certificate Authority - G2" ] = false) do={
+ $LogPrint warning $ScriptName ("Downloading required certificate failed.");
+ :error false;
+ }
+
+ $RandomDelay $TelegramRandomDelay;
+
+ :local Data false;
+ :for I from=1 to=4 do={
+ :if ($Data = false) do={
+ :do {
+ :set Data ([ /tool/fetch check-certificate=yes-without-crl output=user \
+ ("https://api.telegram.org/bot" . $TelegramTokenId . "/getUpdates?offset=" . \
+ $TelegramChatOffset->0 . "&allowed_updates=%5B%22message%22%5D") as-value ]->"data");
+ :set TelegramRandomDelay [ $MAX 0 ($TelegramRandomDelay - 1) ];
+ } on-error={
+ :if ($I < 4) do={
+ $LogPrint debug $ScriptName ("Fetch failed, " . $I . ". try.");
+ :set TelegramRandomDelay [ $MIN 15 ($TelegramRandomDelay + 5) ];
+ :delay (($I * $I) . "s");
+ }
+ }
+ }
+ }
+
+ :if ($Data = false) do={
+ $LogPrint warning $ScriptName ("Failed getting updates from Telegram.");
+ :error false;
+ }
+
+ :local UpdateID 0;
+ :local Uptime [ /system/resource/get uptime ];
+ :foreach UpdateArray in=([ $ParseJson $Data ]->"result") do={
+ :local Update [ $ParseJson $UpdateArray ];
+ :set UpdateID ($Update->"update_id");
+ :local Message [ $ParseJson ($Update->"message") ];
+ :local IsReply [ :len ($Message->"reply_to_message") ];
+ :local IsMyReply ($TelegramMessageIDs->([ $ParseJson ($Message->"reply_to_message") ]->"message_id"));
+ :if (($IsMyReply = 1 || $TelegramChatOffset->0 > 0 || $Uptime > 5m) && $UpdateID >= $TelegramChatOffset->2) do={
+ :local Trusted false;
+ :local Chat [ $ParseJson ($Message->"chat") ];
+ :local From [ $ParseJson ($Message->"from") ];
+
+ :foreach IdsTrusted in=($TelegramChatId, $TelegramChatIdsTrusted) do={
+ :if ($From->"id" = $IdsTrusted || $From->"username" = $IdsTrusted) do={
+ :set Trusted true;
+ }
+ }
+
+ :if ($Trusted = true) do={
+ :local Done false;
+ :if ($Message->"text" = "?") do={
+ $LogPrint info $ScriptName ("Sending notice for update " . $UpdateID . ".");
+ $SendTelegram2 ({ origin=$ScriptName; chatid=($Chat->"id"); silent=true; replyto=($Message->"message_id"); \
+ subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \
+ message=("Online" . [ $IfThenElse $TelegramChatActive " (and active!)" ] . ", awaiting your commands!") });
+ :set Done true;
+ }
+ :if ($Done = false && [ :pick ($Message->"text") 0 1 ] = "!") do={
+ :if ($Message->"text" ~ ("^! *(" . [ $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 = 0 && $TelegramChatActive = true)) && [ :len ($Message->"text") ] > 0) do={
+ :if ([ $ValidateSyntax ($Message->"text") ] = true) do={
+ :local State "";
+ :local File ("tmpfs/telegram-chat/" . [ $GetRandom20CharAlNum 6 ]);
+ :if ([ $MkDir "tmpfs/telegram-chat" ] = false) do={
+ $LogPrint error $ScriptName ("Failed creating directory!");
+ :error false;
+ }
+ $LogPrint info $ScriptName ("Running command from update " . $UpdateID . ": " . $Message->"text");
+ :execute script=(":do {\n" . $Message->"text" . "\n} on-error={ /file/add name=\"" . $File . ".failed\" };" . \
+ "/file/add name=\"" . $File . ".done\"") file=($File . "\00");
+ :if ([ $WaitForFile ($File . ".done") [ $EitherOr $TelegramChatRunTime 20s ] ] = false) do={
+ :set State ([ $SymbolForNotification "warning-sign" ] . "The command did not finish, still running in background.\n\n");
+ }
+ :if ([ :len [ /file/find where name=($File . ".failed") ] ] > 0) do={
+ :set State ([ $SymbolForNotification "cross-mark" ] . "The command failed with an error!\n\n");
+ }
+ :local Content [ /file/get $File contents ];
+ $SendTelegram2 ({ origin=$ScriptName; chatid=($Chat->"id"); silent=true; replyto=($Message->"message_id"); \
+ subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \
+ message=([ $SymbolForNotification "gear" ] . "Command:\n" . $Message->"text" . "\n\n" . \
+ $State . [ $IfThenElse ([ :len $Content ] > 0) \
+ ([ $SymbolForNotification "memo" ] . "Output:\n" . $Content) [ $IfThenElse ([ /file/get $File size ] > 0) \
+ ([ $SymbolForNotification "warning-sign" ] . "Output exceeds file read size.") \
+ ([ $SymbolForNotification "memo" ] . "No output.") ] ]) });
+ /file/remove "tmpfs/telegram-chat";
+ } else={
+ $LogPrint info $ScriptName ("The command from update " . $UpdateID . " failed syntax validation!");
+ $SendTelegram2 ({ origin=$ScriptName; chatid=($Chat->"id"); silent=false; replyto=($Message->"message_id"); \
+ subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \
+ message=([ $SymbolForNotification "gear" ] . "Command:\n" . $Message->"text" . "\n\n" . \
+ [ $SymbolForNotification "cross-mark" ] . "The command failed syntax validation!") });
+ }
+ }
+ } else={
+ :local MessageText ("Received a message from untrusted contact " . \
+ [ $IfThenElse ([ :len ($From->"username") ] = 0) "without username" ("'" . $From->"username" . "'") ] . \
+ " (ID " . $From->"id" . ") in update " . $UpdateID . "!");
+ :if ($Message->"text" ~ ("^! *" . [ $EscapeForRegEx $Identity ] . "\$")) do={
+ $LogPrint warning $ScriptName $MessageText;
+ $SendTelegram2 ({ origin=$ScriptName; chatid=($Chat->"id"); silent=false; replyto=($Message->"message_id"); \
+ subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \
+ message=("You are not trusted.") });
+ } else={
+ $LogPrint info $ScriptName $MessageText;
+ }
+ }
+ } else={
+ $LogPrint debug $ScriptName ("Already handled update " . $UpdateID . ".");
+ }
+ }
+ :set TelegramChatOffset ([ :pick $TelegramChatOffset 1 3 ], \
+ [ $IfThenElse ($UpdateID >= $TelegramChatOffset->2) ($UpdateID + 1) ($TelegramChatOffset->2) ]);
+} on-error={ }
diff --git a/unattended-lte-firmware-upgrade b/unattended-lte-firmware-upgrade.rsc
index 0bac0a2..904f952 100644
--- a/unattended-lte-firmware-upgrade
+++ b/unattended-lte-firmware-upgrade.rsc
@@ -1,16 +1,16 @@
#!rsc by RouterOS
# RouterOS script: unattended-lte-firmware-upgrade
-# Copyright (c) 2018-2021 Christian Hesse <mail@eworm.de>
+# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
#
# schedule unattended lte firmware upgrade
# https://git.eworm.de/cgit/routeros-scripts/about/doc/unattended-lte-firmware-upgrade.md
-:foreach Interface in=[ / interface lte find where running ] do={
+:foreach Interface in=[ /interface/lte/find where running ] do={
:local Firmware;
- :local IntName [ / interface lte get $Interface name ];
+ :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 once as-value ];
} on-error={
:log debug ("Could not get latest LTE firmware version for interface " . $IntName . ".");
}
@@ -18,17 +18,27 @@
:if ([ :typeof $Firmware ] = "array") do={
:if (($Firmware->"installed") != ($Firmware->"latest")) do={
:log info ("Scheduling LTE firmware upgrade for interface " . $IntName . ".");
+
:global LTEFirmwareUpgrade do={
:global LTEFirmwareUpgrade;
:set LTEFirmwareUpgrade;
- / system scheduler remove ($1 . "-firmware-upgrade");
- / interface lte firmware-upgrade $1 upgrade=yes;
- :log info ("LTE firmware upgrade finished, waiting for installation before reset.");
- :delay 150s;
- / interface lte at-chat $1 input="AT+RESET";
- :log info ("Reset device, waiting to finish and reconnect.");
+
+ /system/scheduler/remove ($1 . "-firmware-upgrade");
+ :do {
+ /interface/lte/firmware-upgrade $1 upgrade=yes;
+ :log info ("LTE firmware upgrade on '" . $1 . "' finished, waiting for reset.");
+ :delay 240s;
+ :local Firmware [ /interface/lte/firmware-upgrade $1 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";
+ }
+ } on-error={
+ :log error ("LTE firmware upgrade on '" . $1 . "' failed.");
+ }
}
- / system scheduler add name=($IntName . "-firmware-upgrade") start-time=startup interval=2s \
+
+ /system/scheduler/add name=($IntName . "-firmware-upgrade") start-time=startup interval=2s \
on-event=(":global LTEFirmwareUpgrade; \$LTEFirmwareUpgrade \"" . $IntName . "\";");
} else={
:log info ("The LTE firmware is up to date on interface " . $IntName . ".");
diff --git a/update-gre-address b/update-gre-address
deleted file mode 100644
index af57d2d..0000000
--- a/update-gre-address
+++ /dev/null
@@ -1,31 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: update-gre-address
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# update gre interface remote address with dynamic address from
-# ipsec remote peer
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/update-gre-address.md
-
-:local 0 "update-gre-address";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global LogPrintExit2;
-
-/ interface gre set remote-address=0.0.0.0 disabled=yes [ find where !running !disabled ];
-
-:foreach Peer in=[ / ip ipsec active-peers find ] do={
- :local PeerVal [ / ip ipsec active-peers get $Peer ];
- :local GreInt [ / interface gre find where comment=$PeerVal->"id" ];
- :if ([ :len $GreInt ] > 0) do={
- :local GreIntVal [ / interface gre get $GreInt ];
- :if ([ :typeof ($PeerVal->"dynamic-address") ] = "str" && \
- ($PeerVal->"dynamic-address" != $GreIntVal->"remote-address" || \
- $GreIntVal->"disabled" = true)) do={
- $LogPrintExit2 info $0 ("Updating remote address for interface " . $GreIntVal->"name" . " to " . $PeerVal->"dynamic-address") false;
- / interface gre set remote-address=0.0.0.0 disabled=yes [ find where remote-address=$PeerVal->"dynamic-address" name!=$GreIntVal->"name" ];
- / interface gre set $GreInt remote-address=($PeerVal->"dynamic-address") disabled=no;
- }
- }
-}
diff --git a/update-gre-address.rsc b/update-gre-address.rsc
new file mode 100644
index 0000000..74967cd
--- /dev/null
+++ b/update-gre-address.rsc
@@ -0,0 +1,42 @@
+#!rsc by RouterOS
+# RouterOS script: update-gre-address
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# requires RouterOS, version=7.12
+#
+# 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
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global CharacterReplace;
+ :global LogPrint;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ /interface/gre/set remote-address=0.0.0.0 disabled=yes [ find where !running !disabled ];
+
+ :foreach Peer in=[ /ip/ipsec/active-peers/find ] do={
+ :local PeerVal [ /ip/ipsec/active-peers/get $Peer ];
+ :local GreInt [ /interface/gre/find where comment=($PeerVal->"id") or comment=[ $CharacterReplace ($PeerVal->"id") "CN=" "" ] ];
+ :if ([ :len $GreInt ] > 0) do={
+ :local GreIntVal [ /interface/gre/get $GreInt ];
+ :if ([ :typeof ($PeerVal->"dynamic-address") ] = "str" && \
+ ($PeerVal->"dynamic-address" != $GreIntVal->"remote-address" || \
+ $GreIntVal->"disabled" = true)) do={
+ $LogPrint info $ScriptName ("Updating remote address for interface " . $GreIntVal->"name" . " to " . $PeerVal->"dynamic-address");
+ /interface/gre/set remote-address=0.0.0.0 disabled=yes [ find where remote-address=$PeerVal->"dynamic-address" name!=$GreIntVal->"name" ];
+ /interface/gre/set $GreInt remote-address=($PeerVal->"dynamic-address") disabled=no;
+ }
+ }
+ }
+} on-error={ }
diff --git a/update-tunnelbroker b/update-tunnelbroker
deleted file mode 100644
index e6f50e4..0000000
--- a/update-tunnelbroker
+++ /dev/null
@@ -1,46 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: update-tunnelbroker
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# Michael Gisbers <michael@gisbers.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# update local address of tunnelbroker interface
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/update-tunnelbroker.md
-
-:local 0 "update-tunnelbroker";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global CertificateAvailable;
-:global LogPrintExit2;
-:global ParseKeyValueStore;
-
-:if ([ / ip cloud get ddns-enabled ] != true) do={
- $LogPrintExit2 error $0 ("IP cloud DDNS is not enabled.") true;
-}
-
-# Get the current ip address from cloud
-/ ip cloud force-update;
-:while ([ / ip cloud get status ] != "updated") do={
- :delay 1s;
-}
-:local PublicAddress [ / ip cloud get public-address ];
-
-:foreach Interface in=[ / interface 6to4 find where comment~"^tunnelbroker" !disabled ] do={
- :local InterfaceVal [ / interface 6to4 get $Interface ];
-
- :if ($PublicAddress != $InterfaceVal->"local-address") do={
- :local Comment [ $ParseKeyValueStore ($InterfaceVal->"comment") ];
-
- :if ([ $CertificateAvailable "Starfield Secure Certificate Authority - G2" ] = false) do={
- $LogPrintExit2 error $0 ("Downloading required certificate failed.") true;
- }
- $LogPrintExit2 info $0 ("Local address changed, sending UPDATE to tunnelbroker! New address: " . $PublicAddress) false;
- / tool fetch check-certificate=yes-without-crl \
- ("https://ipv4.tunnelbroker.net/nic/update\?hostname=" . $Comment->"id") \
- user=($Comment->"user") password=($Comment->"pass") output=none as-value;
- / interface 6to4 set $Interface local-address=$PublicAddress;
- } else={
- $LogPrintExit2 debug $0 ("All tunnelbroker configuration is up to date for interface " . $InterfaceVal->"name" . ".") false;
- }
-}
diff --git a/update-tunnelbroker.rsc b/update-tunnelbroker.rsc
new file mode 100644
index 0000000..c76b7ec
--- /dev/null
+++ b/update-tunnelbroker.rsc
@@ -0,0 +1,67 @@
+#!rsc by RouterOS
+# RouterOS script: update-tunnelbroker
+# Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de>
+# Michael Gisbers <michael@gisbers.de>
+# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
+#
+# provides: ppp-on-up
+# requires RouterOS, version=7.12
+#
+# update local address of tunnelbroker interface
+# https://git.eworm.de/cgit/routeros-scripts/about/doc/update-tunnelbroker.md
+
+:global GlobalFunctionsReady;
+:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
+
+:do {
+ :local ScriptName [ :jobname ];
+
+ :global CertificateAvailable;
+ :global LogPrint;
+ :global ParseKeyValueStore;
+ :global ScriptLock;
+
+ :if ([ $ScriptLock $ScriptName ] = false) do={
+ :error false;
+ }
+
+ :if ([ $CertificateAvailable "Starfield Secure Certificate Authority - G2" ] = false) do={
+ $LogPrint error $ScriptName ("Downloading required certificate failed.");
+ :error false;
+ }
+
+ :foreach Interface in=[ /interface/6to4/find where comment~"^tunnelbroker" !disabled ] do={
+ :local Data false;
+ :local InterfaceVal [ /interface/6to4/get $Interface ];
+ :local Comment [ $ParseKeyValueStore ($InterfaceVal->"comment") ];
+
+ :for I from=2 to=0 do={
+ :if ($Data = false) do={
+ :do {
+ :set Data ([ /tool/fetch check-certificate=yes-without-crl \
+ ("https://ipv4.tunnelbroker.net/nic/update?hostname=" . $Comment->"id") \
+ user=($Comment->"user") password=($Comment->"pass") output=user as-value ]->"data");
+ } on-error={
+ $LogPrint debug $ScriptName ("Failed downloading, " . $I . " retries pending.");
+ :delay 2s;
+ }
+ }
+ }
+
+ :if (!($Data ~ "^(good|nochg) ")) do={
+ $LogPrint error $ScriptName ("Failed sending the local address to tunnelbroker or unexpected response!");
+ :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={ }
diff --git a/upload-backup b/upload-backup
deleted file mode 100644
index 15adc1f..0000000
--- a/upload-backup
+++ /dev/null
@@ -1,93 +0,0 @@
-#!rsc by RouterOS
-# RouterOS script: upload-backup
-# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>
-# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
-#
-# create and upload backup and config file
-# https://git.eworm.de/cgit/routeros-scripts/about/doc/upload-backup.md
-
-:local 0 "upload-backup";
-:global GlobalFunctionsReady;
-:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
-
-:global BackupPassword;
-:global BackupRandomDelay;
-:global BackupSendBinary;
-:global BackupSendExport;
-:global BackupUploadPass;
-:global BackupUploadUrl;
-:global BackupUploadUser;
-:global Domain;
-:global Identity;
-
-:global CharacterReplace;
-:global DeviceInfo;
-:global IfThenElse;
-:global LogPrintExit2;
-:global RandomDelay;
-:global ScriptFromTerminal;
-:global SendNotification;
-:global SymbolForNotification;
-:global WaitForFile;
-:global WaitFullyConnected;
-
-:if ($BackupSendBinary != true && \
- $BackupSendExport != true) do={
- $LogPrintExit2 error $0 ("Configured to send neither backup nor config export.") true;
-}
-
-$WaitFullyConnected;
-
-:if ([ $ScriptFromTerminal $0 ] = false && $BackupRandomDelay > 0) do={
- $RandomDelay $BackupRandomDelay;
-}
-
-# filename based on identity
-:local FileName [ $CharacterReplace ($Identity . "." . $Domain) "." "_" ];
-:local BackupFile "none";
-:local ConfigFile "none";
-:local Failed 0;
-
-# binary backup
-:if ($BackupSendBinary = true) do={
- / system backup save encryption=aes-sha256 name=$FileName password=$BackupPassword;
- $WaitForFile ($FileName . ".backup");
-
- :do {
- / tool fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".backup") \
- user=$BackupUploadUser password=$BackupUploadPass src-path=($FileName . ".backup");
- :set BackupFile ($FileName . ".backup");
- } on-error={
- $LogPrintExit2 error $0 ("Uploading backup file failed!") false;
- :set BackupFile "failed";
- :set Failed 1;
- }
-}
-
-# create configuration export
-:if ($BackupSendExport = true) do={
- / export terse file=$FileName;
- $WaitForFile ($FileName . ".rsc");
-
- :do {
- / tool fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".rsc") \
- user=$BackupUploadUser password=$BackupUploadPass src-path=($FileName . ".rsc");
- :set ConfigFile ($FileName . ".rsc");
- } on-error={
- $LogPrintExit2 error $0 ("Uploading configuration export failed!") false;
- :set ConfigFile "failed";
- :set Failed 1;
- }
-}
-
-$SendNotification [ $IfThenElse ($Failed > 0) \
- ([ $SymbolForNotification "warning-sign" ] . "Backup & Config upload with failure") \
- ([ $SymbolForNotification "floppy-disk" ] . "Backup & Config upload") ] \
- ("Backup and config export upload for " . $Identity . ".\n\n" . \
- [ $DeviceInfo ] . "\n\n" . \
- "Backup file: " . $BackupFile . "\n" . \
- "Config file: " . $ConfigFile) "" "true";
-
-:if ($Failed = 1) do={
- :error "An error occured!";
-}