Compare commits

..

52 commits

Author SHA1 Message Date
Christian Hesse
d586205a70 Info on Let's Encrypt change
> Let's Encrypt's Gen Y (YE and YR bulleted below) Cross-Certified
> Subordinate CAs were issued in violation of CCADB policy which requires
> that the serverAuth EKU extension MUST be present in cross-signed
> intermediate certificates issued since June 15th 2025. Root YE and
> YR were issued September 3rd 2025 and are subject to the requirements.

https://community.letsencrypt.org/t/2026-05-08-gen-y-cross-certified-subordinate-cas-missing-serverauth-eku/247105
2026-05-14 22:59:07 +02:00
Christian Hesse
71c190b478 Merge branch 'lets-encrypt-old-chain' into next 2026-05-13 16:19:11 +02:00
Christian Hesse
a3de8aa081 global-functions: $CleanName: add missing colon 2026-05-13 16:19:11 +02:00
Christian Hesse
2a6567135e INITIAL-COMMANDS: Let's Encrypt switched back to old chain 2026-05-13 16:19:11 +02:00
Christian Hesse
7ad60ac704 README: Let's Encrypt switched back to old chain 2026-05-13 16:19:11 +02:00
Christian Hesse
59e0c4460e certs: Let's Encrypt switched back to old chain 2026-05-13 16:19:11 +02:00
Christian Hesse
6f2eb69ee0 global-functions: $ScriptInstallUpdate: Check for both LE certificates
Hmm... 🤨 Let's Encrypt is doing crazy things. My server was switched to
'Root YE' alredy, now it is back to 'ISRG Root X2'... 😳

So let's check for both for now.
2026-05-13 16:19:11 +02:00
Christian Hesse
73f15a2df0 global-functions: $RmFile: ignore "no such item"...
... as this is still racy.
2026-05-07 15:58:07 +02:00
Christian Hesse
57385b5934 global-functions: $RmDir: ignore "no such item"...
... as this is still racy.
2026-05-07 15:58:07 +02:00
Christian Hesse
74d3fc2933 global-functions: $RmFile: remove with find...
... as this is still racy.
2026-05-07 15:58:07 +02:00
Christian Hesse
875df5da76 global-functions: $RmDir: remove with find...
... as this is still racy.
2026-05-07 15:58:07 +02:00
Christian Hesse
3aaba1f408 netwatch-dns: check with eworm.de and eworm.net
This should prevent against DENIC outages...

https://blog.denic.de/en/denic-reports-resolved-dnssec-disruption-affecting-de-domains/
2026-05-06 16:15:17 +02:00
Christian Hesse
0ef11bf11d global-functions: $IsDNSResolving: check with :retry 2026-05-06 16:15:17 +02:00
Christian Hesse
811df5abf2 global-functions: $IsDNSResolving: check with eworm.de and eworm.net
This should prevent against DENIC outages...

https://blog.denic.de/en/denic-reports-resolved-dnssec-disruption-affecting-de-domains/
2026-05-06 10:01:39 +02:00
Christian Hesse
cd4052ba6b Merge branch 'backup-filename-date' into next 2026-04-28 16:37:09 +02:00
Christian Hesse
2f5aa2f337 backup-{email,upload}: add setting in configuration 2026-04-28 16:36:45 +02:00
Christian Hesse
2f621a5981 backup-upload: drop the GMT offset from filename...
... as it is of little help only. Also it is ambiguous due
to signdness being dropped when cleaning the name.
2026-04-28 16:36:31 +02:00
Christian Hesse
fd956519e2 backup-email: drop the GMT offset from filename...
... as it is of little help only. Also it is ambiguous due
to signdness being dropped when cleaning the name.
2026-04-28 16:36:31 +02:00
Christian Hesse
0382a368ec backup-upload: support date & time in filename 2026-04-28 16:36:31 +02:00
Christian Hesse
d54a0d541d backup-email: support date & time in filename 2026-04-28 16:36:31 +02:00
Christian Hesse
174e16502a global-config: update comment on backup options 2026-04-28 16:36:15 +02:00
Christian Hesse
11dc1e2aa1 ipv6-update: log dynamic entry with info, not warning 2026-04-24 14:28:25 +02:00
Christian Hesse
1f460b5bae mod/notification-email: add setting for certificate verification 2026-04-23 09:17:42 +02:00
Christian Hesse
f5dbc27a01 global-functions: $CertificateDownload: extend the check for builtin certificates 2026-04-23 09:17:42 +02:00
Christian Hesse
f69925e537 global-functions: $CertificateAvailable: warn on undefined intended use 2026-04-23 09:17:42 +02:00
Christian Hesse
885129d650 contrib/telegram: add note for dhcpv6-client-lease 2026-04-23 09:17:42 +02:00
Christian Hesse
d00a517c3b mod/ssh-keys-import: drop old property
The property name changed in RouterOS 7.21beta2, so bump required
version to 7.21.
2026-04-16 09:04:20 +02:00
Christian Hesse
303d7565a2 ipv6-update: delay on parallel run...
... instead of just exiting. This makes sense for fast change
valid/invalid/valid, and also for several configured clients.
2026-04-16 09:01:51 +02:00
Christian Hesse
89b9c62e52 ipv6-update: support dhcpv6-client-lease script...
... by adding a provide, and handling global variable
$DHCPv6ClientLeaseVars.
2026-04-15 10:46:35 +02:00
Christian Hesse
3a66eb4192 introduce 'dhcpv6-client-lease' to run scripts on lease 2026-04-15 10:46:35 +02:00
Christian Hesse
e54a4572f7 rename lease-script -> dhcpv4-server-lease 2026-04-14 15:32:19 +02:00
Christian Hesse
6b3f3c1e46 news-and-changes: always end migration code with semicolon 2026-04-14 15:11:47 +02:00
Christian Hesse
736ff98bce doc/ipv6-update: remove extra dot 2026-04-10 20:27:02 +02:00
Christian Hesse
492edcd23f contrib/logo-color: mark the external link 2026-03-25 09:49:12 +01:00
Christian Hesse
d12d7da074 contrib/logo-color: update FireFox url 2026-03-25 09:45:35 +01:00
Christian Hesse
18463bc9d1 contrib/logo-color: use rsc.eworm.de, update screenshots ...
... with FireFox 149.0, which comes with graphical changes.
2026-03-25 09:35:49 +01:00
Christian Hesse
ded600ac4a CERTIFICATES: use rsc.eworm.de, update screenshots ...
... with FireFox 149.0, which comes with graphical changes.
2026-03-25 09:08:24 +01:00
Christian Hesse
8682e237c3 contrib/html: strip question mark from anchor 2026-03-19 14:38:43 +01:00
Christian Hesse
65febff022 contrib/html: break the line 2026-03-19 14:38:43 +01:00
Christian Hesse
65f101eb48 doc/dhcp-to-dns: add a note on reverse lookup
https://github.com/eworm-de/routeros-scripts/discussions/94
https://github.com/eworm-de/routeros-scripts/issues/104
https://github.com/eworm-de/routeros-scripts/issues/120

... and possibly more.
2026-03-19 11:39:31 +01:00
Christian Hesse
6d5274c5b1 check-certificates: change order of conditions...
... to better match both loops.
2026-03-19 09:51:57 +01:00
Christian Hesse
87f7495534 check-certificates: try renew with common-name or subject-alt-name only
One of these is required to assemble the url.
2026-03-19 09:51:57 +01:00
Christian Hesse
428478e690 dhcp-to-dns: remove unused variable 2026-03-16 11:19:25 +01:00
Christian Hesse
460508c931 Merge branch 'check-certificates' into next 2026-03-12 11:35:41 +01:00
Christian Hesse
6aebf5720d check-certificates: give the name used for fetch 2026-03-12 11:35:41 +01:00
Christian Hesse
22c3f70dab check-certificates: match status code in error message
The message used to be:

    Fetch failed with status 404

... but changed recently:

    failure: Status 404, NOT FOUND

The new string is in RouterOS 7.22, and changed in what ever
beta or rc release. Let's just match the status code and hope
for the best.
2026-03-12 11:35:41 +01:00
Christian Hesse
0d5ee6cf23 check-certificates: skip common-name if not available 2026-03-12 11:35:41 +01:00
Christian Hesse
491189ece4 doc/check-certificates: create example certificate with SAN 2026-03-12 11:05:24 +01:00
Christian Hesse
538071631d global-functions: $ScriptInstallUpdate: warn when script exists...
... but is requested to be added.
2026-03-12 10:56:21 +01:00
Christian Hesse
76ae9686cf fw-addr-lists: raw.githubusercontent.com requires 'ISRG Root X1' now 2026-03-12 10:56:21 +01:00
Christian Hesse
fa16e79746 mod/notification-email: suppress another racy condition 2026-03-10 13:13:03 +01:00
Christian Hesse
2567c332de mod/notification-email: always use :find for scheduler
This is still racy... Try to improve.
2026-03-10 13:13:03 +01:00
56 changed files with 444 additions and 228 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

View file

@ -32,13 +32,13 @@ is to use a desktop browser to get that information. This demonstration uses
[Mozilla Firefox ↗️](https://www.mozilla.org/firefox/).
Let's assume we want to make sure the certificate for
[git.eworm.de](https://git.eworm.de/) is available. Open that page in the
browser, then click the *lock* icon in addressbar, followed by "*Connection
secure*".
[rsc.eworm.de](https://rsc.eworm.de/) is available. Open that page in the
browser, then click the *shield* icon in addressbar, followed by
"*Connection secure*".
![screenshot: dialog A](CERTIFICATES.d/01-dialog-A.avif)
The dialog will change, click "*More information*".
The dialog will change, click "*More site information*".
![screenshot: dialog B](CERTIFICATES.d/02-dialog-B.avif)

View file

@ -18,9 +18,9 @@ Run the complete base installation:
{
:local BaseUrl "https://rsc.eworm.de/main/";
:local CertCommonName "Root YE";
:local CertFileName "Root-YE.pem";
:local CertFingerprint "e14ffcad5b0025731006caa43a121a22d8e9700f4fb9cf852f02a708aa5d5666";
:local CertCommonName "ISRG Root X2";
:local CertFileName "ISRG-Root-X2.pem";
:local CertFingerprint "69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470";
:local CertSettings [ /certificate/settings/get ];
:if (!((($CertSettings->"builtin-trust-anchors") = "trusted" || \

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Before After
Before After

View file

@ -126,18 +126,18 @@ If you intend to download the scripts from a
different location (for example from github.com) install the corresponding
certificate chain.
/tool/fetch "https://rsc.eworm.de/main/certs/Root-YE.pem" dst-path="root-ye.pem";
/tool/fetch "https://rsc.eworm.de/main/certs/ISRG-Root-X2.pem" dst-path="isrg-root-x2.pem";
![screenshot: download certs](README.d/01-download-certs.avif)
> **Info**: Note that the command above does *not* verify server
> certificate, so if you want to be safe download with your workstations's
> browser from CA's website and transfer the file to your MikroTik device:
> *Let's Encrypt* / *ISRG* [Root YE ↗️](https://letsencrypt.org/certs/gen-y/root-ye.pem)
> *Let's Encrypt* / *ISRG* [ISRG Root X2 ↗️](https://letsencrypt.org/certs/isrg-root-x2.pem)
Then we import the certificate.
/certificate/import file-name="root-ye.pem" passphrase="";
/certificate/import file-name="isrg-root-x2.pem" passphrase="";
Do not worry that the command is not shown - that happens because it contains
a sensitive property, the passphrase.
@ -145,11 +145,11 @@ a sensitive property, the passphrase.
![screenshot: import certs](README.d/02-import-certs.avif)
For basic verification we rename the certificate and print it by
fingerprint. Make sure exactly this one certificate ("*Root-YE*")
fingerprint. Make sure exactly this one certificate ("*ISRG-Root-X2*")
is shown.
/certificate/set name="Root-YE" [ find where common-name="Root YE" ];
/certificate/print proplist=name,fingerprint where fingerprint="e14ffcad5b0025731006caa43a121a22d8e9700f4fb9cf852f02a708aa5d5666";
/certificate/set name="ISRG-Root-X2" [ find where common-name="ISRG Root X2" ];
/certificate/print proplist=name,fingerprint where fingerprint="69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470";
![screenshot: check certs](README.d/03-check-certs.avif)
@ -258,8 +258,8 @@ Some events can run a script. If you want your DHCP hostnames to be available
in DNS use `dhcp-to-dns` with the events from dhcp server. For a regular
cleanup add a scheduler entry.
$ScriptInstallUpdate dhcp-to-dns,lease-script;
/ip/dhcp-server/set lease-script=lease-script [ find ];
$ScriptInstallUpdate dhcp-to-dns,dhcpv4-server-lease;
/ip/dhcp-server/set lease-script="dhcpv4-server-lease" [ find ];
/system/scheduler/add name="dhcp-to-dns" interval=5m start-time=startup on-event="/system/script/run dhcp-to-dns;";
![screenshot: setup lease script](README.d/12-setup-lease-script.avif)
@ -286,6 +286,8 @@ Available scripts
* [Use wireless network with daily psk](doc/daily-psk.md) (`daily-psk`)
* [Comment DHCP leases with info from access list](doc/dhcp-lease-comment.md) (`dhcp-lease-comment`)
* [Create DNS records for DHCP leases](doc/dhcp-to-dns.md) (`dhcp-to-dns`)
* [Run other scripts on IPv4 DHCP server lease](doc/dhcpv4-server-lease.md) (`dhcpv4-server-lease`)
* [Run other scripts on IPv6 DHCP client lease](doc/dhcpv6-client-lease.md) (`dhcpv6-client-lease`)
* [Automatically upgrade firmware and reboot](doc/firmware-upgrade-reboot.md) (`firmware-upgrade-reboot`)
* [Download, import and update firewall address-lists](doc/fw-addr-lists.md) (`fw-addr-lists`)
* [Wait for global functions und modules](doc/global-wait.md) (`global-wait`)
@ -294,7 +296,6 @@ Available scripts
* [Create DNS records for IPSec peers](doc/ipsec-to-dns.md) (`ipsec-to-dns`)
* [Update configuration on IPv6 prefix change](doc/ipv6-update.md) (`ipv6-update`)
* [Manage IP addresses with bridge status](doc/ip-addr-bridge.md) (`ip-addr-bridge`)
* [Run other scripts on DHCP lease](doc/lease-script.md) (`lease-script`)
* [Manage LEDs dark mode](doc/leds-mode.md) (`leds-day-mode`, `leds-night-mode` & `leds-toggle-mode`)
* [Forward log messages via notification](doc/log-forward.md) (`log-forward`)
* [Mode button with multiple presses](doc/mode-button.md) (`mode-button`)

View file

@ -16,6 +16,7 @@
do={ :error ("Global config and/or functions not ready."); }; } delay=500ms max=50;
:local ScriptName [ :jobname ];
:global BackupFileNameDate;
:global BackupPassword;
:global BackupRandomDelay;
:global BackupSendBinary;
@ -73,7 +74,9 @@
# filename based on identity
:local DirName ("tmpfs/" . $ScriptName);
:local FileName [ $CleanName ($Identity . "." . $Domain) ];
:local Clock [ /system/clock/get ];
:local FileName [ $CleanName ($Identity . "." . $Domain . [ $IfThenElse \
($BackupFileNameDate = true) ("-" . $Clock->"date" . "-" . $Clock->"time") "" ] ) ];
:local FilePath ($DirName . "/" . $FileName);
:local BackupFile "none";
:local ExportFile "none";

View file

@ -17,6 +17,7 @@
do={ :error ("Global config and/or functions not ready."); }; } delay=500ms max=50;
:local ScriptName [ :jobname ];
:global BackupFileNameDate;
:global BackupPassword;
:global BackupRandomDelay;
:global BackupSendBinary;
@ -72,7 +73,9 @@
# filename based on identity
:local DirName ("tmpfs/" . $ScriptName);
:local FileName [ $CleanName ($Identity . "." . $Domain) ];
:local Clock [ /system/clock/get ];
:local FileName [ $CleanName ($Identity . "." . $Domain . [ $IfThenElse \
($BackupFileNameDate = true) ("-" . $Clock->"date" . "-" . $Clock->"time") "" ] ) ];
:local FilePath ($DirName . "/" . $FileName);
:local BackupFile "none";
:local ExportFile "none";

View file

@ -12,12 +12,12 @@ DOMAINS_DUAL = \
cloudflare-dns.com/SSL-com-Root-Certification-Authority-ECC \
dns.google/GTS-Root-RX \
dns.quad9.net/DigiCert-Global-Root-G3 \
git.eworm.de/Root-YE \
git.eworm.de/ISRG-Root-X2 \
gitlab.com/USERTrust-RSA-Certification-Authority \
lists.blocklist.de/GTS-Root-R4 \
matrix.org/GTS-Root-R4 \
raw.githubusercontent.com/USERTrust-RSA-Certification-Authority \
rsc.eworm.de/Root-YE \
raw.githubusercontent.com/ISRG-Root-X1 \
rsc.eworm.de/ISRG-Root-X2 \
upgrade.mikrotik.com/ISRG-Root-X1
DOMAINS_IPV4 = \
1.1.1.1/SSL-com-Root-Certification-Authority-ECC \

View file

@ -60,8 +60,8 @@
http-header-field=({ [ $FetchUserAgentStr $ScriptName ] }) \
($CertRenewUrl . $CertFileName) dst-path=$CertFileName as-value;
} do={
:if ($Err != "Fetch failed with status 404") do={
$LogPrint warning $0 ("Failed fetching certificate: " . $Err);
:if (!($Err ~ "[Ss]tatus 404")) do={
$LogPrint warning $0 ("Failed fetching certificate by '" . $FetchName . "': " . $Err);
}
:error false;
}
@ -164,7 +164,8 @@
}
$WaitFullyConnected;
:foreach Cert in=[ /certificate/find where !revoked !ca !scep-url expires-after<$CertRenewTime ] do={
:foreach Cert in=[ /certificate/find where !revoked !scep-url expires-after<$CertRenewTime \
!ca (common-name or subject-alt-name) ] do={
:local CertVal [ /certificate/get $Cert ];
:local LastName;
:local FetchName;
@ -177,9 +178,11 @@
$LogPrint info $ScriptName ("Attempting to renew certificate '" . ($CertVal->"name") . "'.");
:local ImportSuccess false;
:set LastName ($CertVal->"common-name");
:set FetchName $LastName;
:set ImportSuccess [ $CheckCertificatesDownloadImport $ScriptName $LastName $FetchName ];
:if ([ :len ($CertVal->"common-name") ] > 0) do={
:set LastName ($CertVal->"common-name");
:set FetchName $LastName;
:set ImportSuccess [ $CheckCertificatesDownloadImport $ScriptName $LastName $FetchName ];
}
:foreach SAN in=($CertVal->"subject-alt-name") do={
:if ($ImportSuccess = false) do={
:set LastName [ :pick $SAN ([ :find $SAN ":" ] + 1) [ :len $SAN ] ];
@ -242,8 +245,8 @@
}
}
:foreach Cert in=[ /certificate/find where !revoked !scep-url !(expires-after=[]) \
expires-after<$CertWarnTime !(fingerprint=[]) ] do={
:foreach Cert in=[ /certificate/find where !revoked !scep-url expires-after<$CertWarnTime \
!(expires-after=[]) !(fingerprint=[]) ] do={
:local CertVal [ /certificate/get $Cert ];
:if ([ :len [ /certificate/scep-server/find where ca-cert=($CertVal->"ca") ] ] > 0) do={

View file

@ -3,7 +3,7 @@
# Copyright (c) 2013-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# provides: lease-script, order=40
# provides: dhcpv4-server-lease, order=40
# requires RouterOS, version=7.19
#
# collect wireless mac adresses in access list

View file

@ -3,7 +3,7 @@
# Copyright (c) 2013-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# provides: lease-script, order=40
# provides: dhcpv4-server-lease, order=40
# requires RouterOS, version=7.19
#
# collect wireless mac adresses in access list

View file

@ -3,7 +3,7 @@
# Copyright (c) 2013-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# provides: lease-script, order=40
# provides: dhcpv4-server-lease, order=40
# requires RouterOS, version=7.19
#
# collect wireless mac adresses in access list

View file

@ -3,7 +3,7 @@
# Copyright (c) 2013-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# provides: lease-script, order=40
# provides: dhcpv4-server-lease, order=40
# requires RouterOS, version=7.19
#
# collect wireless mac adresses in access list

View file

@ -13,7 +13,9 @@ sed \
markdown -f toc,idanchor "${1}" | sed \
-e 's/href="\([-_\./[:alnum:]]*\)\.md\(#[-[:alnum:]]*\)\?"/href="\1.html\2"/g' \
-e '/<h[1234] /s| id="\(.*\)">| id="\L\1">|' \
-e '/<h[1234] /s|-2[1789cd]-||g' -e '/<h[1234] /s|--26-amp-3b-||g' \
-e '/<h[1234] /s|--26-amp-3b-||g' \
-e '/<h[1234] /s|-2[1789cd]-||g' \
-e '/<h[1234] /s|-3[f]-||g' \
-e '/^<pre>/s|pre|pre class="code" onclick="CopyToClipboard(this)"|g' \
-e '/The above link may be broken on code hosting sites/s|blockquote|blockquote style="display: none;"|'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

View file

@ -45,7 +45,7 @@ logo and download it.</p>
<p><img src="logo-color.d/browser-03.avif" alt="Screenshot Browser 03"></p>
<p>(This example is with
<a href="https://www.mozilla.org/de/firefox/new/">Firefox</a>. The workflow
<a href="https://www.mozilla.org/firefox/">Mozilla Firefox ↗️</a>. The workflow
for other browsers may differ.)</p>
<p>See how to

View file

@ -91,6 +91,14 @@ Notes
/save dhcp-to-dns Create DNS records for DHCP leases with [dhcp-to-dns](https://rsc.eworm.de/doc/dhcp-to-dns.md).
#### dhcpv4-server-lease
/save dhcpv4-server-lease Run other scripts on IPv4 DHCP server lease with [dhcpv4-server-lease](https://rsc.eworm.de/doc/dhcpv4-server-lease.md).
#### dhcpv6-client-lease
/save dhcpv6-client-lease Run other scripts on IPv6 DHCP client lease with [dhcpv6-client-lease](https://rsc.eworm.de/doc/dhcpv6-client-lease.md).
#### firmware-upgrade-reboot
/save firmware-upgrade-reboot Automatically upgrade firmware and reboot with [firmware-upgrade-reboot](https://rsc.eworm.de/doc/firmware-upgrade-reboot.md).
@ -123,10 +131,6 @@ Notes
/save ip-addr-bridge Manage IP addresses with [ip-addr-bridge](https://rsc.eworm.de/doc/ip-addr-bridge.md).
#### lease-script
/save lease-script Run other scripts on DHCP lease with [lease-script](https://rsc.eworm.de/doc/lease-script.md).
#### leds-mode
/save leds-mode Manage LEDs dark mode with [leds-mode](https://rsc.eworm.de/doc/leds-mode.md).

View file

@ -3,7 +3,7 @@
# Copyright (c) 2013-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# provides: lease-script, order=60
# provides: dhcpv4-server-lease, order=60
# requires RouterOS, version=7.19
#
# update dhcp-server lease comment with infos from access-list

View file

@ -3,7 +3,7 @@
# Copyright (c) 2013-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# provides: lease-script, order=60
# provides: dhcpv4-server-lease, order=60
# requires RouterOS, version=7.19
#
# update dhcp-server lease comment with infos from access-list

View file

@ -3,7 +3,7 @@
# Copyright (c) 2013-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# provides: lease-script, order=60
# provides: dhcpv4-server-lease, order=60
# requires RouterOS, version=7.19
#
# update dhcp-server lease comment with infos from access-list

View file

@ -3,7 +3,7 @@
# Copyright (c) 2013-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# provides: lease-script, order=60
# provides: dhcpv4-server-lease, order=60
# requires RouterOS, version=7.19
#
# update dhcp-server lease comment with infos from access-list

View file

@ -3,7 +3,7 @@
# Copyright (c) 2013-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# provides: lease-script, order=20
# provides: dhcpv4-server-lease, order=20
# requires RouterOS, version=7.19
#
# check DHCP leases and add/remove/update DNS entries
@ -51,7 +51,6 @@
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") ];

65
dhcpv4-server-lease.rsc Normal file
View file

@ -0,0 +1,65 @@
#!rsc by RouterOS
# RouterOS script: dhcpv4-server-lease
# Copyright (c) 2013-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# requires RouterOS, version=7.19
#
# run scripts on IPv4 DHCP server lease
# https://rsc.eworm.de/doc/dhcpv4-server-lease.md
:local ExitOK false;
:onerror Err {
:global GlobalConfigReady; :global GlobalFunctionsReady;
:retry { :if ($GlobalConfigReady != true || $GlobalFunctionsReady != true) \
do={ :error ("Global config and/or functions not ready."); }; } delay=500ms max=50;
:local ScriptName [ :jobname ];
:global Grep;
:global IfThenElse;
:global LogPrint;
:global ParseKeyValueStore;
:global ScriptLock;
:if ([ :typeof $leaseActIP ] = "nothing" || \
[ :typeof $leaseActMAC ] = "nothing" || \
[ :typeof $leaseServerName ] = "nothing" || \
[ :typeof $leaseBound ] = "nothing") do={
$LogPrint error $ScriptName ("This script is supposed to run from ip dhcp-server.");
:set ExitOK true;
:error false;
}
$LogPrint debug $ScriptName ("DHCP Server " . $leaseServerName . " " . [ $IfThenElse ($leaseBound = 0) \
"de" "" ] . "assigned lease " . $leaseActIP . " to " . $leaseActMAC);
:if ([ $ScriptLock $ScriptName 10 ] = false) do={
:set ExitOK true;
:error false;
}
:if ([ :len [ /system/script/job/find where script=$ScriptName ] ] > 1) do={
$LogPrint debug $ScriptName ("More invocations are waiting, exiting early.");
:set ExitOK true;
:error true;
}
:local RunOrder ({});
:foreach Script in=[ /system/script/find where source~("\n# provides: dhcpv4-server-lease\\b") ] do={
:local ScriptVal [ /system/script/get $Script ];
:local Store [ $ParseKeyValueStore [ $Grep ($ScriptVal->"source") ("\23 provides: dhcpv4-server-lease, ") ] ];
:set ($RunOrder->($Store->"order" . "-" . $ScriptVal->"name")) ($ScriptVal->"name");
}
:foreach Order,Script in=$RunOrder do={
:onerror Err {
$LogPrint debug $ScriptName ("Running script with order " . $Order . ": " . $Script);
/system/script/run $Script;
} do={
$LogPrint warning $ScriptName ("Running script '" . $Script . "' failed: " . $Err);
}
}
} do={
:global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
}

63
dhcpv6-client-lease.rsc Normal file
View file

@ -0,0 +1,63 @@
#!rsc by RouterOS
# RouterOS script: dhcpv6-client-lease
# Copyright (c) 2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# requires RouterOS, version=7.19
#
# run scripts on IPv6 DHCP client lease
# https://rsc.eworm.de/doc/dhcpv6-client-lease.md
:local ExitOK false;
:onerror Err {
:global GlobalConfigReady; :global GlobalFunctionsReady;
:retry { :if ($GlobalConfigReady != true || $GlobalFunctionsReady != true) \
do={ :error ("Global config and/or functions not ready."); }; } delay=500ms max=50;
:local ScriptName [ :jobname ];
:global Grep;
:global LogPrint;
:global ParseKeyValueStore;
:global ScriptLock;
:if ([ $ScriptLock $ScriptName 10 ] = false) do={
:set ExitOK true;
:error false;
}
:if (([ :typeof $"na-address" ] = "nothing" || [ :typeof $"na-valid" ] = "nothing") && \
([ :typeof $"pd-prefix" ] = "nothing" || [ :typeof $"pd-valid" ] = "nothing")) do={
$LogPrint error $ScriptName ("This script is supposed to run from ipv6 dhcp-client.");
:set ExitOK true;
:error false;
}
:global DHCPv6ClientLeaseVars {
"na-address"=$"na-address";
"na-valid"=$"na-valid";
"pd-prefix"=$"pd-prefix";
"pd-valid"=$"pd-valid";
"options"=$"options" };
:local RunOrder ({});
:foreach Script in=[ /system/script/find where source~("\n# provides: dhcpv6-client-lease\\b") ] do={
:local ScriptVal [ /system/script/get $Script ];
:local Store [ $ParseKeyValueStore [ $Grep ($ScriptVal->"source") ("\23 provides: dhcpv6-client-lease, ") ] ];
:set ($RunOrder->($Store->"order" . "-" . $ScriptVal->"name")) ($ScriptVal->"name");
}
:foreach Order,Script in=$RunOrder do={
:onerror Err {
$LogPrint debug $ScriptName ("Running script with order " . $Order . ": " . $Script);
/system/script/run $Script;
} do={
$LogPrint warning $ScriptName ("Running script '" . $Script . "' failed: " . $Err);
}
}
:set DHCPv6ClientLeaseVars;
} do={
:global DHCPv6ClientLeaseVars; :set DHCPv6ClientLeaseVars;
:global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
}

View file

@ -34,6 +34,7 @@ Configuration
The configuration goes to `global-config-overlay`, these are the parameters:
* `BackupFileNameDate`: whether to add date & time in filenames
* `BackupSendBinary`: whether to send binary backup
* `BackupSendExport`: whether to send configuration export
* `BackupSendGlobalConfig`: whether to send `global-config-overlay`

View file

@ -40,6 +40,7 @@ Configuration
The configuration goes to `global-config-overlay`, these are the parameters:
* `BackupFileNameDate`: whether to add date & time in filenames
* `BackupSendBinary`: whether to send binary backup
* `BackupSendExport`: whether to send configuration export
* `BackupSendGlobalConfig`: whether to send `global-config-overlay`

View file

@ -85,7 +85,7 @@ Given you have a certificate on you server, you can use `check-certificates`
for the initial import. Just create a *dummy* certificate with short lifetime
that matches criteria to be renewed:
/certificate/add name=example.com common-name=example.com days-valid=1;
/certificate/add name="example.com" common-name="example.com" subject-alt-name="DNS:example.com" days-valid=1;
/certificate/sign example.com;
/system/script/run check-certificates;

View file

@ -64,14 +64,14 @@ Usage and invocation
Run this script from a dhcp server as lease-script to collect the MAC
address when a new address is leased. You may want to use
[lease-script](lease-script.md).
[dhcpv4-server-lease](dhcpv4-server-lease.md).
See also
--------
* [Comment DHCP leases with info from access list](dhcp-lease-comment.md)
* [Create DNS records for DHCP leases](dhcp-to-dns.md)
* [Run other scripts on DHCP lease](lease-script.md)
* [Run other scripts on IPv4 DHCP server lease](dhcpv4-server-lease.md)
---
[⬅️ Go back to main README](../README.md)

View file

@ -50,14 +50,14 @@ Usage and invocation
Run this script from a dhcp server as lease-script to update the comment
just after a new address is leased. You may want to use
[lease-script](lease-script.md).
[dhcpv4-server-lease](dhcpv4-server-lease.md).
See also
--------
* [Collect MAC addresses in wireless access list](collect-wireless-mac.md)
* [Create DNS records for DHCP leases](dhcp-to-dns.md)
* [Run other scripts on DHCP lease](lease-script.md)
* [Run other scripts on IPv4 DHCP server lease](dhcpv4-server-lease.md)
---
[⬅️ Go back to main README](../README.md)

View file

@ -28,7 +28,7 @@ Just install the script:
$ScriptInstallUpdate dhcp-to-dns;
Then run it from dhcp server as lease script. You may want to use
[lease-script](lease-script.md).
[dhcpv4-server-lease](dhcpv4-server-lease.md).
A scheduler cares about cleanup:
@ -80,13 +80,30 @@ Note this information can be configured in wireless access list with
then due to script execution order. Decrease the scheduler interval to
reduce the effect.
Frequently asked questions
--------------------------
### Is it possible to have the hostname in reverse lookup?
It used to be like that in the beginning. But there are way too many special
cases... Devices without hostname, devices with same hostname, devices
switching from one network to another, devices with same hostname in
different network, ...
Fixing one broke another. It never really worked without problems. So
finally the code ended with what we have now.
I know about that side effect and limitation with reverse lookup, but
there's really no (easy) way to get that right without mac address. The
reverse lookup will always give a name based on mac address.
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)
* [Run other scripts on IPv4 DHCP server lease](dhcpv4-server-lease.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)

View file

@ -0,0 +1,54 @@
Run other scripts on IPv4 DHCP server lease
===========================================
[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-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 IPv4 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: dhcpv4-server-lease, order=##
Currently it runs if available, in order:
* [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
-----------------------------
Just install the script:
$ScriptInstallUpdate dhcpv4-server-lease;
... and add it as `lease-script` to your dhcp server:
/ip/dhcp-server/set lease-script="dhcpv4-server-lease" [ find ];
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)

View file

@ -0,0 +1,65 @@
Run other scripts on IPv6 DHCP client lease
===========================================
[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-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 IPv6 DHCP client as lease script. On a
DHCP leasse it runs each script containing the following line, where `##` is
a decimal number for ordering:
# provides: dhcpv6-client-lease, order=##
The lease script is started with some variables injected, but these are not
available in child scripts. However this script makes these variables
available with a global variable. This code is required in child script:
:global EitherOr;
:global DHCPv6ClientLeaseVars;
:local NaAddress [ $EitherOr $"na-address" ($DHCPv6ClientLeaseVars->"na-address") ];
:local NaValid [ $EitherOr $"na-valid" ($DHCPv6ClientLeaseVars->"na-valid") ];
:local PdPrefix [ $EitherOr $"pd-prefix" ($DHCPv6ClientLeaseVars->"pd-prefix") ];
:local PdValid [ $EitherOr $"pd-valid" ($DHCPv6ClientLeaseVars->"pd-valid") ];
:local Options [ $EitherOr $"options" ($DHCPv6ClientLeaseVars->"options") ];
The values are available under different name then, use `$PdPrefix` instead
of `$"pd-prefix"`, and so on. The resulting script supports both, being a
lease script itself or being run as child.
Currently it runs if available, in order:
* [ipv6-update](ipv6-update.md)
Requirements and installation
-----------------------------
Just install the script:
$ScriptInstallUpdate dhcpv6-client-lease;
... and add it as `lease-script` to your dhcp client:
/ipv6/dhcp-client/set lease-script="dhcpv6-client-lease" [ find ];
See also
--------
* [Update configuration on IPv6 prefix change](ipv6-update.md)
---
[⬅️ Go back to main README](../README.md)
[⬆️ Go back to top](#top)

View file

@ -49,24 +49,24 @@ and add a scheduler.
For `wifi`:
$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;
$ScriptInstallUpdate hotspot-to-wpa-cleanup.wifi,dhcpv4-server-lease;
/system/scheduler/add interval=1d name="hotspot-to-wpa-cleanup" on-event="/system/script/run hotspot-to-wpa-cleanup.wifi;" start-time=startup;
For legacy CAPsMAN:
$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;
$ScriptInstallUpdate hotspot-to-wpa-cleanup.capsman,dhcpv4-server-lease;
/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;
/ip/dhcp-server/set lease-script="dhcpv4-server-lease" comment="hotspot-to-wpa=wpa" hotspot-to-wpa;
You can specify the timeout after which a device is removed from leases and
access-list. The default is four weeks.
/ip/dhcp-server/set lease-script=lease-script comment="hotspot-to-wpa=wpa, timeout=2w" hotspot-to-wpa;
/ip/dhcp-server/set lease-script="dhcpv4-server-lease" comment="hotspot-to-wpa=wpa, timeout=2w" hotspot-to-wpa;
Configuration
-------------
@ -117,7 +117,7 @@ passphrase from hotspot credentials.
See also
--------
* [Run other scripts on DHCP lease](lease-script.md)
* [Run other scripts on IPv4 DHCP server lease](dhcpv4-server-lease.md)
---
[⬅️ Go back to main README](../README.md)

View file

@ -55,7 +55,7 @@ create it with:
/ipv6/firewall/address-list/add address=2003:cf:2f0f:de00::/56 comment=ipv6-pool-isp list=extern;
If the dynamic entry exists already you need to remove it before creating
the static one..
the static one.
Address list entries for specific interfaces can be updated as well. The
interface needs to get its address from pool `isp` and the address list entry
@ -77,6 +77,7 @@ start with "`ipv6-pool-`" and actual pool name, followed by a comma,
See also
--------
* [Run other scripts on IPv6 DHCP client lease](dhcpv6-client-lease.md)
* [Run scripts on ppp connection](ppp-on-up.md)
---

View file

@ -1,54 +1 @@
Run other scripts on DHCP lease
===============================
[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-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. 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:
* [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
-----------------------------
Just install the script:
$ScriptInstallUpdate lease-script;
... and add it as `lease-script` to your dhcp server:
/ip/dhcp-server/set lease-script=lease-script [ find ];
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)
This script has been renamed. Please see [dhcpv4-server-lease](dhcpv4-server-lease.md).

View file

@ -37,7 +37,9 @@ 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.
recipient address. Optionally add `EmailServerCertificate` and add the CA
certificate name if you have certificate verification enabled. Finally
reload the configuration.
> **Info**: Copy relevant configuration from
> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to

View file

@ -4,7 +4,7 @@ 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.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.21-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)
@ -38,9 +38,8 @@ import that key:
$SSHKeysImport "ssh-rsa AAAAB3Nza...QYZk8= user" admin;
The third part of the key (`user` in this example) is inherited as
`info` in RouterOS (or `key-owner` with RouterOS 7.20.x and before). Also
the `MD5` fingerprint is recorded, this helps to audit and verify the
available keys.
`info` 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`

View file

@ -36,6 +36,7 @@ Just install the script:
See also
--------
* [Run other scripts on IPv6 DHCP client lease](dhcpv6-client-lease.md)
* [Update configuration on IPv6 prefix change](ipv6-update.md)
* [Update tunnelbroker configuration](update-tunnelbroker.md)

View file

@ -31,6 +31,8 @@
:global EmailGeneralCc "";
#:global EmailGeneralTo "mail@example.com";
#:global EmailGeneralCc "another@example.com,third@example.com";
# Add the CA certificate name here for verification.
:global EmailServerCertificate "";
# You can send Telegram notifications. Register a bot
# and add the token and chat ids here, then install the module:
@ -88,7 +90,9 @@
# Toggle this to disable color output in terminal/cli.
:global TerminalColorOutput true;
# This defines what backups to generate and what password to use.
# This defines whether to add date & time in filenames, what backups to generate,
# the password to use, and what random delay (between 0 and given seconds) to apply.
:global BackupFileNameDate false;
:global BackupSendBinary false;
:global BackupSendExport true;
:global BackupSendGlobalConfig true;
@ -115,7 +119,7 @@
# cert="Root YE" };
{ url="https://raw.githubusercontent.com/stamparm/ipsum/refs/heads/master/levels/4.txt";
# # higher level (decrease the numerical value) for more addresses, and vice versa
cert="USERTrust RSA Certification Authority" };
cert="ISRG Root X1" };
{ url="https://www.dshield.org/block.txt"; cidr="/24";
cert="GTS Root R4" };
{ url="https://lists.blocklist.de/lists/strongips.txt";

View file

@ -15,7 +15,7 @@
# Git commit id & info, expected configuration version
:global CommitId "unknown";
:global CommitInfo "unknown";
:global ExpectedConfigVersion 139;
:global ExpectedConfigVersion 143;
# global variables not to be changed by user
:global GlobalFunctionsReady false;
@ -111,11 +111,13 @@
:local UseFor [ :tostr $2 ];
:global CertificateDownload;
:global EitherOr;
:global LogPrint;
:global ParseKeyValueStore;
:set UseFor [ $EitherOr $UseFor "undefined" ];
:if ([ :len $UseFor ] = 0) do={
$LogPrint warning $0 ("The intended use is undefined!");
:set UseFor "undefined";
}
:if ([ /system/resource/get free-hdd-space ] < 8388608 && \
[ /certificate/settings/get crl-download ] = true && \
@ -189,7 +191,12 @@
$LogPrint warning $0 ("Failed downloading certificate with CommonName '" . $CommonName . \
"' from repository! Trying fallback to mkcert.org...");
:do {
:if ([ :len [ /certificate/find where common-name="ISRG Root X1" ] ] = 0) do={
:local CertSettings [ /certificate/settings/get ];
:if ([ :len [ /certificate/find where common-name="ISRG Root X1" ] ] = 0 && \
!((($CertSettings->"builtin-trust-anchors") = "trusted" || \
($CertSettings->"builtin-trust-store") ~ "fetch" || \
($CertSettings->"builtin-trust-store") = "all") && \
[ :len [ /certificate/builtin/find where common-name="ISRG Root X1" ] ] > 0)) do={
$LogPrint error $0 ("Required certificate is not available.");
:return false;
}
@ -303,7 +310,7 @@
:for I from=0 to=([ :len $Input ] - 1) do={
:local Char [ :pick $Input $I ];
:if ([ :typeof [ find "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" $Char ] ] = "nil") do={
:if ([ :typeof [ :find "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" $Char ] ] = "nil") do={
:do {
:if ([ :len $Return ] = 0) do={
:error true;
@ -805,10 +812,15 @@
# check if DNS is resolving
:set IsDNSResolving do={
:do {
:resolve "low-ttl.eworm.de";
:local I 1;
:retry {
:set I ($I ^ 1);
:resolve ("low-ttl.eworm." . ({ "de"; "net" }->$I));
} delay=50ms max=6;
} on-error={
:return false;
}
:return true;
}
@ -1193,10 +1205,12 @@
}
:onerror Err {
/file/remove $DirName;
/file/remove [ find where name=$DirName ];
} do={
$LogPrint error $0 ("Removing directory '" . $DirName . "' failed: " . $Err);
:return false;
:if (!($Err ~ "no such item")) do={
$LogPrint error $0 ("Removing directory '" . $DirName . "' failed: " . $Err);
:return false;
}
}
:return true;
}
@ -1222,10 +1236,12 @@
}
:onerror Err {
/file/remove $FileName;
/file/remove [ find where name=$FileName ];
} do={
$LogPrint error $0 ("Removing file '" . $FileName . "' failed: " . $Err);
:return false;
:if (!($Err ~ "no such item")) do={
$LogPrint error $0 ("Removing file '" . $FileName . "' failed: " . $Err);
:return false;
}
}
:return true;
}
@ -1285,12 +1301,15 @@
:global SymbolForNotification;
:global ValidateSyntax;
:if ([ $CertificateAvailable "Root YE" "fetch" ] = false) do={
:if ([ $CertificateAvailable "ISRG Root X2" "fetch" ] = false || \
[ $CertificateAvailable "Root YE" "fetch" ] = 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={
:if ([ :len [ /system/script/find where name=$Script ] ] > 0) do={
$LogPrint warning $0 ("Requested to add script '" . $Script . "', but that exists already!");
} else={
$LogPrint info $0 ("Adding new script: " . $Script);
/system/script/add name=$Script owner=$Script source="#!rsc by RouterOS\n" comment=$NewComment;
}

View file

@ -3,7 +3,7 @@
# Copyright (c) 2021-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# provides: lease-script, order=80
# provides: dhcpv4-server-lease, order=80
# requires RouterOS, version=7.19
# requires device-mode, hotspot
#

View file

@ -3,7 +3,7 @@
# Copyright (c) 2021-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# provides: lease-script, order=80
# provides: dhcpv4-server-lease, order=80
# requires RouterOS, version=7.19
# requires device-mode, hotspot
#

View file

@ -3,7 +3,7 @@
# Copyright (c) 2021-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# provides: lease-script, order=80
# provides: dhcpv4-server-lease, order=80
# requires RouterOS, version=7.19
# requires device-mode, hotspot
#

View file

@ -4,6 +4,7 @@
# https://rsc.eworm.de/COPYING.md
#
# requires RouterOS, version=7.19
# provides: dhcpv6-client-lease, order=40
#
# update firewall and dns settings on IPv6 prefix change
# https://rsc.eworm.de/doc/ipv6-update.md
@ -15,16 +16,19 @@
do={ :error ("Global config and/or functions not ready."); }; } delay=500ms max=50;
:local ScriptName [ :jobname ];
:global EitherOr;
:global LogPrint;
:global ParseKeyValueStore;
:global ScriptLock;
:local NaAddress $"na-address";
:local NaValid $"na-valid";
:local PdPrefix $"pd-prefix";
:local PdValid $"pd-valid";
:global DHCPv6ClientLeaseVars;
:if ([ $ScriptLock $ScriptName ] = false) do={
:local NaAddress [ $EitherOr $"na-address" ($DHCPv6ClientLeaseVars->"na-address") ];
:local NaValid [ $EitherOr $"na-valid" ($DHCPv6ClientLeaseVars->"na-valid") ];
:local PdPrefix [ $EitherOr $"pd-prefix" ($DHCPv6ClientLeaseVars->"pd-prefix") ];
:local PdValid [ $EitherOr $"pd-valid" ($DHCPv6ClientLeaseVars->"pd-valid") ];
:if ([ $ScriptLock $ScriptName 10 ] = false) do={
:set ExitOK true;
:error false;
}
@ -50,7 +54,7 @@
:local Pool [ /ipv6/pool/get [ find where prefix=$PdPrefix ] name ];
:if ([ :len [ /ipv6/firewall/address-list/find where comment=("ipv6-pool-" . $Pool) ] ] = 0) do={
/ipv6/firewall/address-list/add list=("ipv6-pool-" . $Pool) address=:: comment=("ipv6-pool-" . $Pool) dynamic=yes;
$LogPrint warning $ScriptName ("Added dynamic ipv6 address list entry for ipv6-pool-" . $Pool);
$LogPrint info $ScriptName ("Added dynamic ipv6 address list entry for ipv6-pool-" . $Pool);
}
:local AddrList [ /ipv6/firewall/address-list/find where comment=("ipv6-pool-" . $Pool) ];
:local OldPrefix [ /ipv6/firewall/address-list/get ($AddrList->0) address ];

View file

@ -1,65 +1,3 @@
#!rsc by RouterOS
# RouterOS script: lease-script
# Copyright (c) 2013-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# requires RouterOS, version=7.19
#
# run scripts on DHCP lease
# https://rsc.eworm.de/doc/lease-script.md
:local ExitOK false;
:onerror Err {
:global GlobalConfigReady; :global GlobalFunctionsReady;
:retry { :if ($GlobalConfigReady != true || $GlobalFunctionsReady != true) \
do={ :error ("Global config and/or functions not ready."); }; } delay=500ms max=50;
:local ScriptName [ :jobname ];
:global Grep;
:global IfThenElse;
:global LogPrint;
:global ParseKeyValueStore;
:global ScriptLock;
:if ([ :typeof $leaseActIP ] = "nothing" || \
[ :typeof $leaseActMAC ] = "nothing" || \
[ :typeof $leaseServerName ] = "nothing" || \
[ :typeof $leaseBound ] = "nothing") do={
$LogPrint error $ScriptName ("This script is supposed to run from ip dhcp-server.");
:set ExitOK true;
:error false;
}
$LogPrint debug $ScriptName ("DHCP Server " . $leaseServerName . " " . [ $IfThenElse ($leaseBound = 0) \
"de" "" ] . "assigned lease " . $leaseActIP . " to " . $leaseActMAC);
:if ([ $ScriptLock $ScriptName 10 ] = false) do={
:set ExitOK true;
:error false;
}
:if ([ :len [ /system/script/job/find where script=$ScriptName ] ] > 1) do={
$LogPrint debug $ScriptName ("More invocations are waiting, exiting early.");
:set ExitOK true;
:error true;
}
:local RunOrder ({});
:foreach Script in=[ /system/script/find where source~("\n# provides: lease-script\\b") ] do={
:local ScriptVal [ /system/script/get $Script ];
:local Store [ $ParseKeyValueStore [ $Grep ($ScriptVal->"source") ("\23 provides: lease-script, ") ] ];
:set ($RunOrder->($Store->"order" . "-" . $ScriptVal->"name")) ($ScriptVal->"name");
}
:foreach Order,Script in=$RunOrder do={
:onerror Err {
$LogPrint debug $ScriptName ("Running script with order " . $Order . ": " . $Script);
/system/script/run $Script;
} do={
$LogPrint warning $ScriptName ("Running script '" . $Script . "' failed: " . $Err);
}
}
} do={
:global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
}
# dummy for migration

View file

@ -37,7 +37,9 @@
# flush e-mail queue
:set FlushEmailQueue do={ :onerror Err {
:global EmailQueue;
:global EmailServerCertificate;
:global CertificateAvailable;
:global EitherOr;
:global EMailGenerateFrom;
:global FileExists;
@ -48,11 +50,10 @@
:local AllDone true;
:local QueueLen [ :len $EmailQueue ];
:local Scheduler [ /system/scheduler/find where name="_FlushEmailQueue" ];
:if ([ :len $Scheduler ] > 0 && $QueueLen = 0) do={
:if ([ :len [ /system/scheduler/find where name="_FlushEmailQueue" ] ] > 0 && $QueueLen = 0) do={
$LogPrint warning $0 ("Flushing E-Mail messages from scheduler, but queue is empty.");
/system/scheduler/remove $Scheduler;
/system/scheduler/remove [ find where name="_FlushEmailQueue" ];
:return false;
}
@ -60,15 +61,19 @@
:return true;
}
:if ([ :len $Scheduler ] < 0) do={
:if ([ :len [ /system/scheduler/find where name="_FlushEmailQueue" ] ] < 0) do={
/system/scheduler/add name="_FlushEmailQueue" interval=1m start-time=startup \
comment="Doing initial checks..." on-event=(":global FlushEmailQueue; \$FlushEmailQueue;");
:set Scheduler [ /system/scheduler/find where name="_FlushEmailQueue" ];
}
:local SchedVal [ /system/scheduler/get $Scheduler ];
:if (($SchedVal->"interval") < 1m) do={
/system/scheduler/set interval=1m comment="Doing initial checks..." $Scheduler;
:do {
:if (([ /system/scheduler/get [ find where name="_FlushEmailQueue" ] ]->"interval") < 1m) do={
/system/scheduler/set interval=1m comment="Doing initial checks..." \
[ find where name="_FlushEmailQueue" ];
}
} on-error={
$LogPrint debug $0 ("The scheduler is gone, aborting.");
:return false;
}
:if ([ /tool/e-mail/get last-status ] = "in-progress") do={
@ -87,7 +92,16 @@
:return false;
}
/system/scheduler/set interval=($QueueLen . "m") comment="Sending..." $Scheduler;
:if (([ /tool/e-mail/get ]->"certificate-verification") ~ "^yes" && \
[ :len $EmailServerCertificate ] > 0) do={
:if ([ $CertificateAvailable $EmailServerCertificate "email" ] = false) do={
$LogPrint warning $0 ("Downloading required certificate failed.");
:return false;
}
}
/system/scheduler/set interval=($QueueLen . "m") comment="Sending..." \
[ find where name="_FlushEmailQueue" ];
:foreach Id,Message in=$EmailQueue do={
:if ([ :typeof $Message ] = "array" ) do={
@ -143,8 +157,8 @@
:return false;
}
/system/scheduler/set interval=(($SchedVal->"run-count") . "m") \
comment="Waiting for retry..." $Scheduler;
/system/scheduler/set interval=(([ get [ find where name="_FlushEmailQueue" ] ]->"run-count") . "m") \
comment="Waiting for retry..." [ find where name="_FlushEmailQueue" ];
} do={
:global ExitOnError; $ExitOnError $0 $Err;
} }

View file

@ -3,7 +3,7 @@
# Copyright (c) 2020-2026 Christian Hesse <mail@eworm.de>
# https://rsc.eworm.de/COPYING.md
#
# requires RouterOS, version=7.19
# requires RouterOS, version=7.21
#
# import ssh keys for public key authentication
# https://rsc.eworm.de/doc/mod/ssh-keys-import.md
@ -40,9 +40,8 @@
:local FingerPrintMD5 [ :convert from=base64 transform=md5 to=hex ($KeyVal->1) ];
:local RegEx ("\\bmd5=" . $FingerPrintMD5 . "\\b");
:if ([ :len [ /user/ssh-keys/find where user=$User \
(key-owner~$RegEx or info~$RegEx) ] ] > 0) do={
info~("\\bmd5=" . $FingerPrintMD5 . "\\b") ] ] > 0) do={
$LogPrint warning $0 ("The ssh public key (MD5:" . $FingerPrintMD5 . \
") is already available for user '" . $User . "'.");
:return false;

View file

@ -115,13 +115,15 @@
:local Data false;
:onerror Err {
:local I 1;
:retry {
:set I ($I ^ 1);
: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");
} delay=1s max=3;
"\01\00" . "\00\01" . "\00\00" . "\00\00" . "\00\00" . "\09doh-check\05eworm" . \
({ "\02de"; "\03net" }->$I) . "\00" . "\00\10" . "\00\01") ]) as-value ]->"data");
} delay=500ms max=6;
} do={
$LogPrint warning $ScriptName ("Request to DoH server " . ($DohServer->"doh-url") . \
" failed: " . $Err);

View file

@ -64,14 +64,19 @@
137="Added support to send notifications via Gotify (gotify.net).";
138="RouterOS 7.19 is suffering an issue with certificate store. Fixing trust state for all certificates...";
139="Certificate Authorities will reduce the leaf certificate validity times soon. Thus the defaults for renewal and warning in 'check-certificates' were decreased.";
140="The scripts 'lease-script' was renamed to 'dhcpv4-server-lease', configuration was updated automatically.";
141="Introduced script 'dhcpv6-client-lease' to run several scripts on IPv6 DHCP client lease.";
142="Added a setting for 'mod/notification-email' to check availability of certificate chain.";
143="Made backup scripts 'backup-email' and 'backup-upload' support date & time in filenames.";
};
# Migration steps to be applied on script updates
:global GlobalConfigMigration {
97=":local Rec [ /ip/dns/static/find where comment~\"^managed by dhcp-to-dns for \" ]; :if ([ :len \$Rec ] > 0) do={ /ip/dns/static/remove \$Rec; /system/script/run dhcp-to-dns; }";
100=":global ScriptInstallUpdate; :if ([ :len [ /system/script/find where name=\"ssh-keys-import\" source~\"^#!rsc by RouterOS\\r?\\n\" ] ] > 0) do={ /system/script/set name=\"mod/ssh-keys-import\" ssh-keys-import; \$ScriptInstallUpdate; }";
97=":local Rec [ /ip/dns/static/find where comment~\"^managed by dhcp-to-dns for \" ]; :if ([ :len \$Rec ] > 0) do={ /ip/dns/static/remove \$Rec; /system/script/run dhcp-to-dns; };";
100=":global ScriptInstallUpdate; :if ([ :len [ /system/script/find where name=\"ssh-keys-import\" source~\"^#!rsc by RouterOS\\r?\\n\" ] ] > 0) do={ /system/script/set name=\"mod/ssh-keys-import\" ssh-keys-import; \$ScriptInstallUpdate; };";
104=":global CharacterReplace; :global ScriptInstallUpdate; :foreach Script in={ \"capsman-download-packages\"; \"capsman-rolling-upgrade\"; \"hotspot-to-wpa\"; \"hotspot-to-wpa-cleanup\" } do={ /system/script/set name=(\$Script . \".capsman\") [ find where name=\$Script ]; :foreach Scheduler in=[ /system/scheduler/find where on-event~(\$Script . \"([^-.]|\\\$)\") ] do={ /system/scheduler/set \$Scheduler on-event=[ \$CharacterReplace [ get \$Scheduler on-event ] \$Script (\$Script . \".capsman\") ]; }; }; /ip/hotspot/user/profile/set on-login=\"hotspot-to-wpa.capsman\" [ find where on-login=\"hotspot-to-wpa\" ]; \$ScriptInstallUpdate;";
111=":local Rec [ /ip/dns/static/find where comment~\"^managed by dhcp-to-dns for \" ]; :if ([ :len \$Rec ] > 0) do={ /ip/dns/static/remove \$Rec; /system/script/run dhcp-to-dns; }";
132=":if ([ :len [ /system/script/find where name=\"check-health\" ] ] > 0) do={ :local Code \":local Install \\\"check-health\\\"; :if ([ :len [ /system/health/find where type=\\\"\\\" name~\\\"-state\\\\\\\$\\\" ] ] > 0) do={ :set Install (\\\$Install . \\\",check-health.d/state\\\"); }; :if ([ :len [ /system/health/find where type=\\\"C\\\" ] ] > 0) do={ :set Install (\\\$Install . \\\",check-health.d/temperature\\\"); }; :if ([ :len [ /system/health/find where type=\\\"V\\\" ] ] > 0) do={ :set Install (\\\$Install . \\\",check-health.d/voltage\\\"); }; :global ScriptInstallUpdate; \\\$ScriptInstallUpdate \\\$Install;\"; :global ValidateSyntax; :if ([ \$ValidateSyntax \$Code ] = true) do={ :do { [ :parse \$Code ]; } on-error={ }; }; }";
111=":local Rec [ /ip/dns/static/find where comment~\"^managed by dhcp-to-dns for \" ]; :if ([ :len \$Rec ] > 0) do={ /ip/dns/static/remove \$Rec; /system/script/run dhcp-to-dns; };";
132=":if ([ :len [ /system/script/find where name=\"check-health\" ] ] > 0) do={ :local Code \":local Install \\\"check-health\\\"; :if ([ :len [ /system/health/find where type=\\\"\\\" name~\\\"-state\\\\\\\$\\\" ] ] > 0) do={ :set Install (\\\$Install . \\\",check-health.d/state\\\"); }; :if ([ :len [ /system/health/find where type=\\\"C\\\" ] ] > 0) do={ :set Install (\\\$Install . \\\",check-health.d/temperature\\\"); }; :if ([ :len [ /system/health/find where type=\\\"V\\\" ] ] > 0) do={ :set Install (\\\$Install . \\\",check-health.d/voltage\\\"); }; :global ScriptInstallUpdate; \\\$ScriptInstallUpdate \\\$Install;\"; :global ValidateSyntax; :if ([ \$ValidateSyntax \$Code ] = true) do={ :do { [ :parse \$Code ]; } on-error={ }; }; };";
138="/certificate/set trusted=yes [ find where trusted=yes ];";
140=":if ([ :len [ /system/script/find where name=\"lease-script\" ] ] > 0) do={ /system/script/set name=\"dhcpv4-server-lease\" \"lease-script\"; :global ScriptInstallUpdate; \$ScriptInstallUpdate; /ip/dhcp-server/set lease-script=\"dhcpv4-server-lease\" [ find where lease-script=\"lease-script\" ]; };";
};