| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | #!rsc by RouterOS | 
					
						
							|  |  |  | # RouterOS script: global-functions | 
					
						
							| 
									
										
										
										
											2024-01-01 15:25:25 +01:00
										 |  |  | # Copyright (c) 2013-2024 Christian Hesse <mail@eworm.de> | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | #                         Michael Gisbers <michael@gisbers.de> | 
					
						
							|  |  |  | # https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md | 
					
						
							|  |  |  | # | 
					
						
							| 
									
										
										
										
											2024-03-27 23:01:38 +01:00
										 |  |  | # requires RouterOS, version=7.13 | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | # | 
					
						
							|  |  |  | # global functions | 
					
						
							|  |  |  | # https://git.eworm.de/cgit/routeros-scripts/about/ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-12 15:19:42 +01:00
										 |  |  | :local ScriptName [ :jobname ]; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | # expected configuration version | 
					
						
							| 
									
										
										
										
											2024-03-28 21:29:27 +01:00
										 |  |  | :global ExpectedConfigVersion 126; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | # global variables not to be changed by user | 
					
						
							|  |  |  | :global GlobalFunctionsReady false; | 
					
						
							|  |  |  | :global Identity [ /system/identity/get name ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # global functions | 
					
						
							| 
									
										
										
										
											2023-11-23 14:41:46 +01:00
										 |  |  | :global AlignRight; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | :global CertificateAvailable; | 
					
						
							|  |  |  | :global CertificateDownload; | 
					
						
							|  |  |  | :global CertificateNameByCN; | 
					
						
							| 
									
										
										
										
											2023-12-04 10:56:48 +01:00
										 |  |  | :global CharacterMultiply; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | :global CharacterReplace; | 
					
						
							|  |  |  | :global CleanFilePath; | 
					
						
							| 
									
										
										
										
											2024-01-31 15:27:13 +01:00
										 |  |  | :global CleanName; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | :global DeviceInfo; | 
					
						
							|  |  |  | :global Dos2Unix; | 
					
						
							|  |  |  | :global DownloadPackage; | 
					
						
							|  |  |  | :global EitherOr; | 
					
						
							|  |  |  | :global EscapeForRegEx; | 
					
						
							| 
									
										
										
										
											2024-03-27 23:01:38 +01:00
										 |  |  | :global FetchHuge; | 
					
						
							| 
									
										
										
										
											2024-03-29 11:09:22 +01:00
										 |  |  | :global FetchUserAgentStr; | 
					
						
							| 
									
										
										
										
											2023-04-20 23:15:04 +02:00
										 |  |  | :global FormatLine; | 
					
						
							| 
									
										
										
										
											2023-09-15 22:50:32 +02:00
										 |  |  | :global FormatMultiLines; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | :global GetMacVendor; | 
					
						
							|  |  |  | :global GetRandom20CharAlNum; | 
					
						
							|  |  |  | :global GetRandom20CharHex; | 
					
						
							|  |  |  | :global GetRandomNumber; | 
					
						
							|  |  |  | :global Grep; | 
					
						
							|  |  |  | :global HexToNum; | 
					
						
							| 
									
										
										
										
											2023-11-15 11:36:47 +01:00
										 |  |  | :global HumanReadableNum; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | :global IfThenElse; | 
					
						
							|  |  |  | :global IsDefaultRouteReachable; | 
					
						
							|  |  |  | :global IsDNSResolving; | 
					
						
							|  |  |  | :global IsFullyConnected; | 
					
						
							|  |  |  | :global IsMacLocallyAdministered; | 
					
						
							|  |  |  | :global IsTimeSync; | 
					
						
							| 
									
										
										
										
											2024-03-08 10:38:09 +01:00
										 |  |  | :global LogPrint; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | :global LogPrintExit2; | 
					
						
							| 
									
										
										
										
											2023-10-27 08:42:32 +02:00
										 |  |  | :global LogPrintOnce; | 
					
						
							| 
									
										
										
										
											2023-11-30 12:01:59 +01:00
										 |  |  | :global MAX; | 
					
						
							| 
									
										
										
										
											2023-11-30 12:02:51 +01:00
										 |  |  | :global MIN; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | :global MkDir; | 
					
						
							|  |  |  | :global NotificationFunctions; | 
					
						
							| 
									
										
										
										
											2023-05-10 13:44:11 +02:00
										 |  |  | :global ParseDate; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | :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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-23 14:41:46 +01:00
										 |  |  | # align string to the right | 
					
						
							| 
									
										
										
										
											2024-03-27 23:00:27 +01:00
										 |  |  | :set AlignRight do={ | 
					
						
							| 
									
										
										
										
											2023-11-23 14:41:46 +01:00
										 |  |  |   :local Input [ :tostr $1 ]; | 
					
						
							|  |  |  |   :local Len   [ :tonum $2 ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-04 10:58:27 +01:00
										 |  |  |   :global CharacterMultiply; | 
					
						
							| 
									
										
										
										
											2023-11-23 14:41:46 +01:00
										 |  |  |   :global EitherOr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :set Len [ $EitherOr $Len 8 ]; | 
					
						
							| 
									
										
										
										
											2023-12-04 10:58:27 +01:00
										 |  |  |   :local Spaces [ $CharacterMultiply " " $Len ]; | 
					
						
							| 
									
										
										
										
											2023-11-23 14:41:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :return ([ :pick $Spaces 0 ($Len - [ :len $Input ]) ] . $Input); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | # check and download required certificate | 
					
						
							|  |  |  | :set CertificateAvailable do={ | 
					
						
							|  |  |  |   :local CommonName [ :tostr $1 ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :global CertificateDownload; | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   :global LogPrint; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :global ParseKeyValueStore; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ([ /system/resource/get free-hdd-space ] < 8388608 && \ | 
					
						
							|  |  |  |        [ /certificate/settings/get crl-download ] = true && \ | 
					
						
							|  |  |  |        [ /certificate/settings/get crl-store ] = "system") do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint warning $0 ("This system has low free flash space but " . \ | 
					
						
							|  |  |  |       "is configured to download certificate CRLs to system!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ([ :len [ /certificate/find where common-name=$CommonName ] ] = 0) do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint info $0 ("Certificate with CommonName \"" . $CommonName . "\" not available."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     :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={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint info $0 ("Certificate chain for \"" . $CommonName . \ | 
					
						
							|  |  |  |         "\" is incomplete, missing \"" . ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") . "\"."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |       :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; | 
					
						
							| 
									
										
										
										
											2024-03-16 23:27:45 +01:00
										 |  |  |   :global CleanName; | 
					
						
							| 
									
										
										
										
											2024-03-29 11:09:22 +01:00
										 |  |  |   :global FetchUserAgentStr; | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   :global LogPrint; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :global WaitForFile; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   $LogPrint info $0 ("Downloading and importing certificate with " . \ | 
					
						
							|  |  |  |       "CommonName \"" . $CommonName . "\"."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :do { | 
					
						
							| 
									
										
										
										
											2024-03-16 23:27:45 +01:00
										 |  |  |     :local FileName ([ $CleanName $CommonName ] . ".pem"); | 
					
						
							| 
									
										
										
										
											2024-03-29 11:09:22 +01:00
										 |  |  |     /tool/fetch check-certificate=yes-without-crl http-header-field=({ [ $FetchUserAgentStr $0 ] }) \ | 
					
						
							| 
									
										
										
										
											2024-03-16 23:27:45 +01:00
										 |  |  |       ($ScriptUpdatesBaseUrl . "certs/" . $FileName . $ScriptUpdatesUrlSuffix) \ | 
					
						
							|  |  |  |       dst-path=$FileName as-value; | 
					
						
							|  |  |  |     $WaitForFile $FileName; | 
					
						
							|  |  |  |     /certificate/import file-name=$FileName passphrase="" as-value; | 
					
						
							| 
									
										
										
										
											2024-01-08 00:25:55 +01:00
										 |  |  |     :delay 1s; | 
					
						
							| 
									
										
										
										
											2024-03-16 23:27:45 +01:00
										 |  |  |     /file/remove $FileName; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-16 23:27:45 +01:00
										 |  |  |     :foreach Cert in=[ /certificate/find where name~("^" . $FileName . "_[0-9]+\$") ] do={ | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |       $CertificateNameByCN [ /certificate/get $Cert common-name ]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } on-error={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint warning $0 ("Failed importing certificate with CommonName \"" . $CommonName . "\"!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     :return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   :return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # name a certificate by its common-name | 
					
						
							|  |  |  | :set CertificateNameByCN do={ | 
					
						
							|  |  |  |   :local CommonName [ :tostr $1 ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-31 15:31:07 +01:00
										 |  |  |   :global CleanName; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :local Cert [ /certificate/find where common-name=$CommonName ]; | 
					
						
							| 
									
										
										
										
											2024-01-31 15:31:07 +01:00
										 |  |  |   /certificate/set $Cert name=[ $CleanName $CommonName ]; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-04 10:56:48 +01:00
										 |  |  | # multiply given character(s) | 
					
						
							|  |  |  | :set CharacterMultiply do={ | 
					
						
							|  |  |  |   :local Return ""; | 
					
						
							|  |  |  |   :for I from=1 to=$2 do={ | 
					
						
							|  |  |  |     :set Return ($Return . $1); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   :return $Return; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | # 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; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-31 15:27:13 +01:00
										 |  |  | # 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; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | # get readable device info | 
					
						
							|  |  |  | :set DeviceInfo do={ | 
					
						
							|  |  |  |   :global ExpectedConfigVersion; | 
					
						
							|  |  |  |   :global Identity; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :global IfThenElse; | 
					
						
							| 
									
										
										
										
											2023-04-20 23:44:56 +02:00
										 |  |  |   :global FormatLine; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-04 09:28:59 +01:00
										 |  |  |   :local License [ /system/license/get ]; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :local Resource [ /system/resource/get ]; | 
					
						
							|  |  |  |   :local RouterBoard; | 
					
						
							|  |  |  |   :do { | 
					
						
							|  |  |  |     :set RouterBoard [[ :parse "/system/routerboard/get" ]]; | 
					
						
							|  |  |  |   } on-error={ } | 
					
						
							| 
									
										
										
										
											2023-12-04 09:28:59 +01:00
										 |  |  |   :local Snmp [ /snmp/get ]; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :local Update [ /system/package/update/get ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :return ( \ | 
					
						
							| 
									
										
										
										
											2023-04-20 23:44:56 +02:00
										 |  |  |     [ $FormatLine "Hostname" $Identity ] . "\n" . \ | 
					
						
							| 
									
										
										
										
											2023-12-04 09:28:59 +01:00
										 |  |  |     [ $IfThenElse ([ :len ($Snmp->"location") ] > 0) \ | 
					
						
							|  |  |  |       ([ $FormatLine "Location" ($Snmp->"location") ] . "\n") ] . \ | 
					
						
							|  |  |  |     [ $IfThenElse ([ :len ($Snmp->"contact") ] > 0) \ | 
					
						
							|  |  |  |       ([ $FormatLine "Contact" ($Snmp->"contact") ] . "\n") ] . \ | 
					
						
							| 
									
										
										
										
											2023-04-20 23:44:56 +02:00
										 |  |  |     [ $FormatLine "Board name" ($Resource->"board-name") ] . "\n" . \ | 
					
						
							|  |  |  |     [ $FormatLine "Architecture" ($Resource->"architecture-name") ] . "\n" . \ | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     [ $IfThenElse ($RouterBoard->"routerboard" = true) \ | 
					
						
							| 
									
										
										
										
											2023-04-20 23:44:56 +02:00
										 |  |  |       ([ $FormatLine "Model" ($RouterBoard->"model") ] . \ | 
					
						
							|  |  |  |        [ $IfThenElse ([ :len ($RouterBoard->"revision") ] > 0) \ | 
					
						
							|  |  |  |            (" " . $RouterBoard->"revision") ] . "\n" . \ | 
					
						
							|  |  |  |        [ $FormatLine "Serial number" ($RouterBoard->"serial-number") ] . "\n") ] . \ | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     [ $IfThenElse ([ :len ($License->"level") ] > 0) \ | 
					
						
							| 
									
										
										
										
											2023-04-20 23:44:56 +02:00
										 |  |  |       ([ $FormatLine "License" ($License->"level") ] . "\n") ] . \ | 
					
						
							|  |  |  |     "RouterOS:\n" . \ | 
					
						
							|  |  |  |     [ $FormatLine "    Channel" ($Update->"channel") ] . "\n" . \ | 
					
						
							|  |  |  |     [ $FormatLine "    Installed" ($Update->"installed-version") ] . "\n" . \ | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     [ $IfThenElse ([ :typeof ($Update->"latest-version") ] != "nothing" && \ | 
					
						
							|  |  |  |         $Update->"installed-version" != $Update->"latest-version") \ | 
					
						
							| 
									
										
										
										
											2023-04-20 23:44:56 +02:00
										 |  |  |       ([ $FormatLine "    Available" ($Update->"latest-version") ] . "\n") ] . \ | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     [ $IfThenElse ($RouterBoard->"routerboard" = true && \ | 
					
						
							|  |  |  |         $RouterBoard->"current-firmware" != $RouterBoard->"upgrade-firmware") \ | 
					
						
							| 
									
										
										
										
											2023-04-20 23:44:56 +02:00
										 |  |  |       ([ $FormatLine "    Firmware" ($RouterBoard->"current-firmware") ] . "\n") ] . \ | 
					
						
							|  |  |  |     "RouterOS-Scripts:\n" . \ | 
					
						
							|  |  |  |     [ $FormatLine "    Version" $ExpectedConfigVersion ]); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # 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; | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   :global LogPrint; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :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={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint warning $0 ("Failed creating directory, not downloading package."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     :return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ([ :len [ /file/find where name=$PkgDest type="package" ] ] > 0) do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint info $0 ("Package file " . $PkgName . " already exists."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     :return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ([ $CertificateAvailable "R3" ] = false) do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint error $0 ("Downloading required certificate failed."); | 
					
						
							| 
									
										
										
										
											2024-03-05 15:59:22 +01:00
										 |  |  |     :return false; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :local Url ("https://upgrade.mikrotik.com/routeros/" . $PkgVer . "/" . $PkgFile); | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   $LogPrint info $0 ("Downloading package file '" . $PkgName . "'..."); | 
					
						
							|  |  |  |   $LogPrint debug $0 ("... from url: " . $Url); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :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={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint debug $0 ("Downloading package file failed."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /file/remove [ find where name=$PkgDest ]; | 
					
						
							|  |  |  |     :set Retry ($Retry - 1); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   $LogPrint warning $0 ("Downloading package file '" . $PkgName . "' failed."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :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 ]; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-09-15 09:46:28 +02:00
										 |  |  |   :if ([ :typeof $1 ] = "time") do={ | 
					
						
							|  |  |  |     :return [ $IfThenElse ($1 > 0s) $1 $2 ]; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :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 ""; | 
					
						
							| 
									
										
										
										
											2023-06-26 11:14:55 +02:00
										 |  |  |   :local Chars ("^.[]\$()|*+?{}\\"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :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; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-27 23:01:38 +01:00
										 |  |  | # fetch huge data to file, read in chunks | 
					
						
							|  |  |  | :set FetchHuge do={ | 
					
						
							|  |  |  |   :local ScriptName [ :tostr $1 ]; | 
					
						
							|  |  |  |   :local Url        [ :tostr $2 ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :global GetRandom20CharAlNum; | 
					
						
							|  |  |  |   :global LogPrint; | 
					
						
							|  |  |  |   :global MkDir; | 
					
						
							|  |  |  |   :global WaitForFile; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ([ $MkDir "tmpfs/" . $ScriptName ] = false) do={ | 
					
						
							|  |  |  |     $LogPrint error $0 ("Failed creating directory!"); | 
					
						
							|  |  |  |     :return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :local FileName ("tmpfs/" . $ScriptName . "/" . $0 . "-" . [ $GetRandom20CharAlNum ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :do { | 
					
						
							|  |  |  |     /tool/fetch check-certificate=yes-without-crl $Url dst-path=$FileName as-value; | 
					
						
							|  |  |  |   } on-error={ | 
					
						
							|  |  |  |     $LogPrint debug $0 ("Failed downloading from: " . $Url); | 
					
						
							|  |  |  |     :return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   $WaitForFile $FileName; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :local FileSize [ /file/get $FileName size ]; | 
					
						
							|  |  |  |   :local Return ""; | 
					
						
							|  |  |  |   :local VarSize 0; | 
					
						
							|  |  |  |   :while ($VarSize < $FileSize) do={ | 
					
						
							|  |  |  |     :set Return ($Return . ([ /file/read offset=$VarSize chunk-size=32768 file=$FileName as-value ]->"data")); | 
					
						
							|  |  |  |     :set VarSize [ :len $Return ]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   /file/remove $FileName; | 
					
						
							|  |  |  |   :return $Return; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-16 23:10:47 +01:00
										 |  |  | # generate user agent string for fetch | 
					
						
							| 
									
										
										
										
											2024-03-29 11:09:22 +01:00
										 |  |  | :set FetchUserAgentStr do={ | 
					
						
							| 
									
										
										
										
											2024-03-16 23:10:47 +01:00
										 |  |  |   :local Caller [ :tostr $1 ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :local Resource [ /system/resource/get ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :return ("User-Agent: Mikrotik/" . $Resource->"version" . " " . \ | 
					
						
							|  |  |  |     $Resource->"architecture-name" . " " . $Caller . "/Fetch (https://rsc.eworm.de/)"); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-20 23:15:04 +02:00
										 |  |  | # format a line for output | 
					
						
							|  |  |  | :set FormatLine do={ | 
					
						
							| 
									
										
										
										
											2023-09-15 22:50:32 +02:00
										 |  |  |   :local Key    [ :tostr $1 ]; | 
					
						
							|  |  |  |   :local Value  [ :tostr $2 ]; | 
					
						
							|  |  |  |   :local Indent [ :tonum $3 ]; | 
					
						
							| 
									
										
										
										
											2023-12-04 10:59:41 +01:00
										 |  |  |   :local Spaces; | 
					
						
							| 
									
										
										
										
											2023-04-20 23:15:04 +02:00
										 |  |  |   :local Return ""; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-04 10:59:41 +01:00
										 |  |  |   :global CharacterMultiply; | 
					
						
							| 
									
										
										
										
											2023-04-20 23:15:04 +02:00
										 |  |  |   :global EitherOr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :set Indent [ $EitherOr $Indent 16 ]; | 
					
						
							| 
									
										
										
										
											2023-12-04 10:59:41 +01:00
										 |  |  |   :local Spaces [ $CharacterMultiply " " $Indent ]; | 
					
						
							| 
									
										
										
										
											2023-04-20 23:15:04 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :if ([ :len $Key ] > 0) do={ :set Return ($Key . ":"); } | 
					
						
							|  |  |  |   :if ([ :len $Key ] > ($Indent - 2)) do={ | 
					
						
							| 
									
										
										
										
											2023-09-15 22:50:32 +02:00
										 |  |  |     :set Return ($Return . "\n" . [ :pick $Spaces 0 $Indent ] . $Value); | 
					
						
							| 
									
										
										
										
											2023-04-20 23:15:04 +02:00
										 |  |  |   } else={ | 
					
						
							| 
									
										
										
										
											2023-09-15 22:50:32 +02:00
										 |  |  |     :set Return ($Return . [ :pick $Spaces 0 ($Indent - [ :len $Return ]) ] . $Value); | 
					
						
							| 
									
										
										
										
											2023-04-20 23:15:04 +02:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-09-15 22:50:32 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :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 ]; | 
					
						
							| 
									
										
										
										
											2023-04-20 23:15:04 +02:00
										 |  |  |   :foreach Value in=[ :pick $Values 1 [ :len $Values ] ] do={ | 
					
						
							| 
									
										
										
										
											2023-09-15 22:50:32 +02:00
										 |  |  |     :set Return ($Return . "\n" . [ $FormatLine "" $Value $Indent ]); | 
					
						
							| 
									
										
										
										
											2023-04-20 23:15:04 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :return $Return; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | # get MAC vendor | 
					
						
							|  |  |  | :set GetMacVendor do={ | 
					
						
							|  |  |  |   :local Mac [ :tostr $1 ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :global CertificateAvailable; | 
					
						
							|  |  |  |   :global IsMacLocallyAdministered; | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   :global LogPrint; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :if ([ $IsMacLocallyAdministered $Mac ] = true) do={ | 
					
						
							|  |  |  |     :return "locally administered"; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :do { | 
					
						
							| 
									
										
										
										
											2023-12-22 14:47:54 +01:00
										 |  |  |     :if ([ $CertificateAvailable "GTS CA 1P5" ] = false) do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint warning $0 ("Downloading required certificate failed."); | 
					
						
							|  |  |  |       :error false; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |     :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; | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint debug $0 ("The mac vendor is not known in database."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     } on-error={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint warning $0 ("Failed getting mac vendor."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |     :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 ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-16 22:20:19 +01:00
										 |  |  |   :global HexToNum; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ([ :pick $Input 0 ] = "*") do={ | 
					
						
							|  |  |  |     :return [ $HexToNum [ :pick  $Input 1 [ :len $Input ] ] ]; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-16 22:20:19 +01:00
										 |  |  |   :return [ :tonum ("0x" . $Input) ]; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-15 11:36:47 +01:00
										 |  |  | # return human readable number | 
					
						
							| 
									
										
										
										
											2024-03-27 23:01:07 +01:00
										 |  |  | :set HumanReadableNum do={ | 
					
						
							| 
									
										
										
										
											2023-11-15 11:36:47 +01:00
										 |  |  |   :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); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | # 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; | 
					
						
							| 
									
										
										
										
											2023-06-22 14:21:52 +02:00
										 |  |  |   :global IsTimeSyncResetNtp; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   :global LogPrint; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :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; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-06-22 14:21:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-23 21:32:00 +02:00
										 |  |  |     :if ([ :typeof $IsTimeSyncResetNtp ] = "nothing") do={ | 
					
						
							|  |  |  |       :set IsTimeSyncResetNtp 0s; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     :local Uptime [ /system/resource/get uptime ]; | 
					
						
							|  |  |  |     :if ($Uptime - $IsTimeSyncResetNtp < 3m) do={ | 
					
						
							| 
									
										
										
										
											2023-06-22 14:21:52 +02:00
										 |  |  |       :return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-23 21:32:00 +02:00
										 |  |  |     :set IsTimeSyncResetNtp $Uptime; | 
					
						
							| 
									
										
										
										
											2023-06-22 14:21:52 +02:00
										 |  |  |     /system/ntp/client/set enabled=no; | 
					
						
							|  |  |  |     :delay 20ms; | 
					
						
							|  |  |  |     /system/ntp/client/set enabled=yes; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     :return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ([ /system/license/get ]->"level" = "free" || \ | 
					
						
							|  |  |  |        [ /system/resource/get ]->"board-name" = "x86") do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint debug $0 ("No ntp client configured, relying on RTC for CHR free license and x86."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     :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; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   $LogPrint debug $0 ("No time source configured! Returning gracefully..."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-08 10:38:09 +01:00
										 |  |  | # log and print with same text | 
					
						
							|  |  |  | :set LogPrint do={ | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :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); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2024-03-08 10:38:09 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # 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; | 
					
						
							| 
									
										
										
										
											2024-03-15 14:42:50 +01:00
										 |  |  |   :global LogPrintOnce; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   $LogPrintOnce warning $0 \ | 
					
						
							|  |  |  |     ("This function is deprecated and will be removed. Please make your adjustments!"); | 
					
						
							| 
									
										
										
										
											2024-03-08 10:38:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   $LogPrint $1 $2 $3; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :if ($Exit = "true") do={ | 
					
						
							|  |  |  |     :error ("Hard error to exit."); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-27 08:42:32 +02:00
										 |  |  | # log and print, once until reboot | 
					
						
							|  |  |  | :set LogPrintOnce do={ | 
					
						
							|  |  |  |   :local Severity [ :tostr $1 ]; | 
					
						
							|  |  |  |   :local Name     [ :tostr $2 ]; | 
					
						
							|  |  |  |   :local Message  [ :tostr $3 ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   :global LogPrint; | 
					
						
							| 
									
										
										
										
											2023-10-27 08:42:32 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :global LogPrintOnceMessages; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ([ :typeof $LogPrintOnceMessages ] = "nothing") do={ | 
					
						
							|  |  |  |     :set LogPrintOnceMessages ({}); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ($LogPrintOnceMessages->$Message = 1) do={ | 
					
						
							| 
									
										
										
										
											2024-03-26 13:51:44 +01:00
										 |  |  |     :return false; | 
					
						
							| 
									
										
										
										
											2023-10-27 08:42:32 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-27 07:43:32 +01:00
										 |  |  |   :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!"); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-27 08:42:32 +02:00
										 |  |  |   :set ($LogPrintOnceMessages->$Message) 1; | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   $LogPrint $Severity $Name $Message; | 
					
						
							| 
									
										
										
										
											2024-03-26 13:51:44 +01:00
										 |  |  |   :return true; | 
					
						
							| 
									
										
										
										
											2023-10-27 08:42:32 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-30 12:01:59 +01:00
										 |  |  | # get max value | 
					
						
							|  |  |  | :set MAX do={ | 
					
						
							|  |  |  |   :if ($1 > $2) do={ :return $1; } | 
					
						
							|  |  |  |   :return $2; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-30 12:02:51 +01:00
										 |  |  | # get min value | 
					
						
							|  |  |  | :set MIN do={ | 
					
						
							|  |  |  |   :if ($1 < $2) do={ :return $1; } | 
					
						
							|  |  |  |   :return $2; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | # create directory | 
					
						
							|  |  |  | :set MkDir do={ | 
					
						
							|  |  |  |   :local Path [ :tostr $1 ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :global CleanFilePath; | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   :global LogPrint; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :global WaitForFile; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-25 21:27:24 +01:00
										 |  |  |   :local MkTmpfs do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     :global LogPrint; | 
					
						
							| 
									
										
										
										
											2023-03-25 21:27:24 +01:00
										 |  |  |     :global WaitForFile; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :if ([ :len [ /disk/find where slot=tmpfs type=tmpfs ] ] = 1) do={ | 
					
						
							|  |  |  |       :return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint info $0 ("Creating disk of type tmpfs."); | 
					
						
							| 
									
										
										
										
											2023-03-25 21:27:24 +01:00
										 |  |  |     /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={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint warning $0 ("Creating disk of type tmpfs failed!"); | 
					
						
							| 
									
										
										
										
											2023-03-25 21:27:24 +01:00
										 |  |  |       :return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     :return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :set Path [ $CleanFilePath $Path ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ($Path = "") do={ | 
					
						
							|  |  |  |     :return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ([ :len [ /file/find where name=$Path type="directory" ] ] = 1) do={ | 
					
						
							|  |  |  |     :return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-28 15:59:23 +02:00
										 |  |  |   :if ([ :pick $Path 0 5 ] = "tmpfs") do={ | 
					
						
							|  |  |  |     :if ([ $MkTmpfs ] = false) do={ | 
					
						
							| 
									
										
										
										
											2023-03-25 21:59:40 +01:00
										 |  |  |       :return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-03-28 15:59:23 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :do { | 
					
						
							|  |  |  |     :local File ($Path . "/file"); | 
					
						
							|  |  |  |     /file/add name=$File; | 
					
						
							|  |  |  |     $WaitForFile $File; | 
					
						
							|  |  |  |     /file/remove $File; | 
					
						
							|  |  |  |   } on-error={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint warning $0 ("Making directory '" . $Path . "' failed!"); | 
					
						
							| 
									
										
										
										
											2023-03-28 15:59:23 +02:00
										 |  |  |     :return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # prepare NotificationFunctions array | 
					
						
							|  |  |  | :if ([ :typeof $NotificationFunctions ] != "array") do={ | 
					
						
							|  |  |  |   :set NotificationFunctions ({}); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-10 13:44:11 +02:00
										 |  |  | # parse the date and return a named array | 
					
						
							|  |  |  | :set ParseDate do={ | 
					
						
							|  |  |  |   :local Date [ :tostr $1 ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-10 14:42:48 +02:00
										 |  |  |   :return ({ "year"=[ :tonum [ :pick $Date 0 4 ] ]; | 
					
						
							|  |  |  |             "month"=[ :tonum [ :pick $Date 5 7 ] ]; | 
					
						
							|  |  |  |               "day"=[ :tonum [ :pick $Date 8 10 ] ] }); | 
					
						
							| 
									
										
										
										
											2023-05-10 13:44:11 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | # 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={ | 
					
						
							| 
									
										
										
										
											2024-01-29 21:34:08 +01:00
										 |  |  |   :local Time [ :tonum $1 ]; | 
					
						
							|  |  |  |   :local Unit [ :tostr $2 ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :global EitherOr; | 
					
						
							|  |  |  |   :global GetRandomNumber; | 
					
						
							| 
									
										
										
										
											2024-01-29 22:32:57 +01:00
										 |  |  |   :global MAX; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-29 21:36:14 +01:00
										 |  |  |   :if ($Time = 0) do={ | 
					
						
							|  |  |  |     :return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-29 22:32:57 +01:00
										 |  |  |   :delay ([ $MAX 10 [ $GetRandomNumber ([ :tonsec [ :totime ($Time . [ $EitherOr $Unit "s" ]) ] ] / 1000000) ] ] . "ms"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # check for required RouterOS version | 
					
						
							|  |  |  | :set RequiredRouterOS do={ | 
					
						
							|  |  |  |   :local Caller   [ :tostr $1 ]; | 
					
						
							|  |  |  |   :local Required [ :tostr $2 ]; | 
					
						
							|  |  |  |   :local Warn     [ :tostr $3 ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :global IfThenElse; | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   :global LogPrint; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :global VersionToNum; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-30 20:50:55 +02:00
										 |  |  |   :if (!($Required ~ "^\\d+\\.\\d+((alpha|beta|rc|\\.)\\d+|)\$")) do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint error $0 ("No valid RouterOS version: " . $Required); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     :return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ([ $VersionToNum $Required ] > [ $VersionToNum [ /system/package/update/get installed-version ] ]) do={ | 
					
						
							|  |  |  |     :if ($Warn = "true") do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint warning $0 ("This " . [ $IfThenElse ([ :pick $Caller 0 ] = ("\$")) "function" "script" ] . \ | 
					
						
							|  |  |  |         " '" . $Caller . "' (at least specific functionality) requires RouterOS " . $Required . ". Please update!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |     :return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   :return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # check if script is run from terminal | 
					
						
							|  |  |  | :set ScriptFromTerminal do={ | 
					
						
							|  |  |  |   :local Script [ :tostr $1 ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   :global LogPrint; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :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={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint debug $0 ("Script " . $Script . " started from terminal."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |       :return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   $LogPrint debug $0 ("Script " . $Script . " NOT started from terminal."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :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; | 
					
						
							| 
									
										
										
										
											2024-03-29 11:09:22 +01:00
										 |  |  |   :global FetchUserAgentStr; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :global Grep; | 
					
						
							|  |  |  |   :global IfThenElse; | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   :global LogPrint; | 
					
						
							| 
									
										
										
										
											2024-01-30 07:18:38 +01:00
										 |  |  |   :global LogPrintOnce; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :global ParseKeyValueStore; | 
					
						
							|  |  |  |   :global RequiredRouterOS; | 
					
						
							|  |  |  |   :global SendNotification2; | 
					
						
							|  |  |  |   :global SymbolForNotification; | 
					
						
							|  |  |  |   :global ValidateSyntax; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ([ $CertificateAvailable "E1" ] = false) do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint warning $0 ("Downloading certificate failed, trying without."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :foreach Script in=$Scripts do={ | 
					
						
							|  |  |  |     :if ([ :len [ /system/script/find where name=$Script ] ] = 0) do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint info $0 ("Adding new script: " . $Script); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |       /system/script/add name=$Script owner=$Script source="#!rsc by RouterOS\n" comment=$NewComment; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :local ExpectedConfigVersionBefore $ExpectedConfigVersion; | 
					
						
							|  |  |  |   :local ReloadGlobalFunctions false; | 
					
						
							|  |  |  |   :local ReloadGlobalConfig false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-31 23:55:12 +02:00
										 |  |  |   :foreach Script in=[ /system/script/find where source~"^#!rsc by RouterOS\r?\n" ] do={ | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     :local ScriptVal [ /system/script/get $Script ]; | 
					
						
							| 
									
										
										
										
											2023-10-13 17:27:52 +02:00
										 |  |  |     :local ScriptInfo [ $ParseKeyValueStore ($ScriptVal->"comment") ]; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     :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={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |         $LogPrint warning $0 ("Policies differ for script '" . $ScriptVal->"name" . \ | 
					
						
							|  |  |  |           "' and its scheduler '" . $SchedulerVal->"name" . "'!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-13 17:29:25 +02:00
										 |  |  |     :if (!($ScriptInfo->"ignore" = true)) do={ | 
					
						
							|  |  |  |       :do { | 
					
						
							| 
									
										
										
										
											2023-10-13 17:35:30 +02:00
										 |  |  |         :local BaseUrl [ $EitherOr ($ScriptInfo->"base-url") $ScriptUpdatesBaseUrl ]; | 
					
						
							|  |  |  |         :local UrlSuffix [ $EitherOr ($ScriptInfo->"url-suffix") $ScriptUpdatesUrlSuffix ]; | 
					
						
							| 
									
										
										
										
											2023-10-13 17:29:25 +02:00
										 |  |  |         :local Url ($BaseUrl . $ScriptVal->"name" . ".rsc" . $UrlSuffix); | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |         $LogPrint debug $0 ("Fetching script '" . $ScriptVal->"name" . "' from url: " . $Url); | 
					
						
							| 
									
										
										
										
											2023-10-13 17:29:25 +02:00
										 |  |  |         :local Result [ /tool/fetch check-certificate=yes-without-crl \ | 
					
						
							| 
									
										
										
										
											2024-03-29 11:09:22 +01:00
										 |  |  |           http-header-field=({ [ $FetchUserAgentStr $0 ] }) $Url output=user as-value ]; | 
					
						
							| 
									
										
										
										
											2023-10-13 17:29:25 +02:00
										 |  |  |         :if ($Result->"status" = "finished") do={ | 
					
						
							|  |  |  |           :set SourceNew ($Result->"data"); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } on-error={ | 
					
						
							|  |  |  |         :if ($ScriptVal->"source" = "#!rsc by RouterOS\n") do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |           $LogPrint warning $0 ("Failed fetching script '" . $ScriptVal->"name" . \ | 
					
						
							|  |  |  |             "', removing dummy. Typo on installation?"); | 
					
						
							| 
									
										
										
										
											2023-10-13 17:29:25 +02:00
										 |  |  |           /system/script/remove $Script; | 
					
						
							|  |  |  |         } else={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |           $LogPrint warning $0 ("Failed fetching script '" . $ScriptVal->"name" . "'!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-10-13 17:29:25 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     :if ([ :len $SourceNew ] > 0) do={ | 
					
						
							|  |  |  |       :if ($SourceNew != $ScriptVal->"source") do={ | 
					
						
							|  |  |  |         :if ([ :pick $SourceNew 0 18 ] = "#!rsc by RouterOS\n") do={ | 
					
						
							| 
									
										
										
										
											2023-03-07 12:08:30 +01:00
										 |  |  |           :local Required ([ $ParseKeyValueStore [ $Grep $SourceNew ("\23 requires RouterOS, ") ] ]->"version"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |           :if ([ $RequiredRouterOS $0 [ $EitherOr $Required "0.0" ] false ] = true) do={ | 
					
						
							|  |  |  |             :if ([ $ValidateSyntax $SourceNew ] = true) do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |               $LogPrint info $0 ("Updating script: " . $ScriptVal->"name"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |               /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={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |               $LogPrint warning $0 ("Syntax validation for script '" . $ScriptVal->"name" . \ | 
					
						
							|  |  |  |                 "' failed! Ignoring!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |           } else={ | 
					
						
							| 
									
										
										
										
											2024-01-30 07:18:38 +01:00
										 |  |  |             $LogPrintOnce warning $0 ("The script '" . $ScriptVal->"name" . "' requires RouterOS " . \ | 
					
						
							| 
									
										
										
										
											2024-03-08 11:09:22 +01:00
										 |  |  |               $Required . ", which is not met by your installation. Ignoring!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |           } | 
					
						
							|  |  |  |         } else={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |           $LogPrint warning $0 ("Looks like new script '" . $ScriptVal->"name" . \ | 
					
						
							|  |  |  |             "' is not valid (missing shebang). Ignoring!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |       } else={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |         $LogPrint debug $0 ("Script '" .  $ScriptVal->"name" . "' did not change."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |       } | 
					
						
							|  |  |  |     } else={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint debug $0 ("No update for script '" . $ScriptVal->"name" . "'."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ($ReloadGlobalFunctions = true) do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint info $0 ("Reloading global functions."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     :do { | 
					
						
							|  |  |  |       /system/script/run global-functions; | 
					
						
							|  |  |  |     } on-error={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint error $0 ("Reloading global functions failed!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ($ReloadGlobalConfig = true) do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint info $0 ("Reloading global configuration."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     :do { | 
					
						
							|  |  |  |       /system/script/run global-config; | 
					
						
							|  |  |  |     } on-error={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint error $0 ("Reloading global configuration failed!" . \ | 
					
						
							|  |  |  |         " Syntax error or missing overlay?"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ($ExpectedConfigVersionBefore > $ExpectedConfigVersion) do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint warning $0 ("The configuration version decreased from " . \ | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |       $ExpectedConfigVersionBefore . " to " . $ExpectedConfigVersion . \ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       ". Installed an older version?"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ($ExpectedConfigVersionBefore < $ExpectedConfigVersion) do={ | 
					
						
							|  |  |  |     :global GlobalConfigChanges; | 
					
						
							|  |  |  |     :global GlobalConfigMigration; | 
					
						
							|  |  |  |     :local ChangeLogCode; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :do { | 
					
						
							|  |  |  |       :local Url ($ScriptUpdatesBaseUrl . "news-and-changes.rsc" . $ScriptUpdatesUrlSuffix); | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint debug $0 ("Fetching news, changes and migration: " . $Url); | 
					
						
							| 
									
										
										
										
											2023-10-10 17:38:15 +02:00
										 |  |  |       :local Result [ /tool/fetch check-certificate=yes-without-crl \ | 
					
						
							| 
									
										
										
										
											2024-03-29 11:09:22 +01:00
										 |  |  |         http-header-field=({ [ $FetchUserAgentStr $0 ] }) $Url output=user as-value ]; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |       :if ($Result->"status" = "finished") do={ | 
					
						
							|  |  |  |         :set ChangeLogCode ($Result->"data"); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } on-error={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint warning $0 ("Failed fetching news, changes and migration!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :if ([ :len $ChangeLogCode ] > 0) do={ | 
					
						
							|  |  |  |       :if ([ $ValidateSyntax $ChangeLogCode ] = true) do={ | 
					
						
							|  |  |  |         :do { | 
					
						
							|  |  |  |           [ :parse $ChangeLogCode ]; | 
					
						
							|  |  |  |         } on-error={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |           $LogPrint warning $0 ("The changelog failed to run!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |       } else={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |         $LogPrint warning $0 ("The changelog failed syntax validation!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :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={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |             $LogPrint info $0 ("Applying migration for change " . $I . ": " . $Migration); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |             :do { | 
					
						
							|  |  |  |               [ :parse $Migration ]; | 
					
						
							|  |  |  |             } on-error={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |               $LogPrint warning $0 ("Migration code for change " . $I . " failed to run!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |           } else={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |             $LogPrint warning $0 ("Migration code for change " . $I . " failed syntax validation!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :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."); | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint info $0 ($NotificationMessage); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     :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 " . \ | 
					
						
							| 
									
										
										
										
											2023-11-29 13:20:15 +01:00
										 |  |  |             [ $SymbolForNotification "pushpin" "*" ] . $Change); | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |         $LogPrint info $0 ("Change " . $I . ": " . $Change); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |       } | 
					
						
							|  |  |  |     } 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."); | 
					
						
							| 
									
										
										
										
											2024-03-15 10:05:32 +01:00
										 |  |  |         :set Link "https://rsc.eworm.de/#donate"; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       $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 ]; | 
					
						
							| 
									
										
										
										
											2024-03-05 16:12:36 +01:00
										 |  |  |   :local WaitMax ([ :tonum $3 ] * 10); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :global GetRandom20CharAlNum; | 
					
						
							|  |  |  |   :global IfThenElse; | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   :global LogPrint; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :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={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint error $0 ("A script named '" . $Script . "' does not exist!"); | 
					
						
							|  |  |  |     :error false; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ([ $JobCount $Script ] = 0) do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint error $0 ("No script '" . $Script . "' is running!"); | 
					
						
							|  |  |  |     :error false; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   :if ([ $TicketCount $Script ] >= [ $JobCount $Script ]) do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint error $0 ("More tickets than running scripts '" . $Script . "', resetting!"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     :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; | 
					
						
							| 
									
										
										
										
											2024-03-05 16:12:36 +01:00
										 |  |  |     :return true; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   $RemoveTicket $Script $MyTicket; | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |   $LogPrint info $0 ("Script '" . $Script . "' started more than once" . [ $IfThenElse ($WaitCount > 0) \ | 
					
						
							|  |  |  |     " and timed out waiting for lock" "" ] . "..."); | 
					
						
							| 
									
										
										
										
											2024-03-05 16:12:36 +01:00
										 |  |  |   :return false; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # 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={ | 
					
						
							| 
									
										
										
										
											2024-01-20 00:09:54 +01:00
										 |  |  |   :local Name [ :tostr $1 ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-19 23:57:09 +01:00
										 |  |  |   :global LogPrintOnce; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :local Symbols { | 
					
						
							|  |  |  |     "abacus"="\F0\9F\A7\AE"; | 
					
						
							|  |  |  |     "alarm-clock"="\E2\8F\B0"; | 
					
						
							| 
									
										
										
										
											2023-11-27 18:38:57 +01:00
										 |  |  |     "arrow-down"="\E2\AC\87"; | 
					
						
							| 
									
										
										
										
											2023-11-29 14:13:10 +01:00
										 |  |  |     "arrow-up"="\E2\AC\86"; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     "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"; | 
					
						
							| 
									
										
										
										
											2024-03-21 13:39:56 +01:00
										 |  |  |     "gear"="\E2\9A\99"; | 
					
						
							| 
									
										
										
										
											2023-11-27 18:38:57 +01:00
										 |  |  |     "heart"="\E2\99\A5"; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     "high-voltage-sign"="\E2\9A\A1"; | 
					
						
							|  |  |  |     "incoming-envelope"="\F0\9F\93\A8"; | 
					
						
							| 
									
										
										
										
											2023-10-10 23:43:59 +02:00
										 |  |  |     "information"="\E2\84\B9"; | 
					
						
							|  |  |  |     "large-orange-circle"="\F0\9F\9F\A0"; | 
					
						
							|  |  |  |     "large-red-circle"="\F0\9F\94\B4"; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     "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"; | 
					
						
							| 
									
										
										
										
											2024-03-14 22:12:03 +01:00
										 |  |  |     "smiley-partying-face"="\F0\9F\A5\B3"; | 
					
						
							|  |  |  |     "smiley-smiling-face"="\E2\98\BA"; | 
					
						
							|  |  |  |     "smiley-winking-face-with-tongue"="\F0\9F\98\9C"; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     "sparkles"="\E2\9C\A8"; | 
					
						
							|  |  |  |     "speech-balloon"="\F0\9F\92\AC"; | 
					
						
							| 
									
										
										
										
											2023-11-27 18:30:11 +01:00
										 |  |  |     "star"="\E2\AD\90"; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     "warning-sign"="\E2\9A\A0"; | 
					
						
							|  |  |  |     "white-heavy-check-mark"="\E2\9C\85" | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-19 23:57:09 +01:00
										 |  |  |   :if ([ :len ($Symbols->$Name) ] = 0) do={ | 
					
						
							| 
									
										
										
										
											2024-03-08 11:09:22 +01:00
										 |  |  |     $LogPrintOnce warning $0 ("No symbol available for name '" . $Name . "'!"); | 
					
						
							| 
									
										
										
										
											2024-01-19 23:57:09 +01:00
										 |  |  |     :return ""; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-20 00:09:54 +01:00
										 |  |  |   :return (($Symbols->$Name) . "\EF\B8\8F"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # return symbol for notification | 
					
						
							|  |  |  | :set SymbolForNotification do={ | 
					
						
							|  |  |  |   :global NotificationsWithSymbols; | 
					
						
							|  |  |  |   :global SymbolByUnicodeName; | 
					
						
							| 
									
										
										
										
											2023-11-29 13:20:15 +01:00
										 |  |  |   :global IfThenElse; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :if ($NotificationsWithSymbols != true) do={ | 
					
						
							| 
									
										
										
										
											2023-11-29 13:20:15 +01:00
										 |  |  |     :return [ $IfThenElse ([ :len $2 ] > 0) ([ :tostr $2 ] . " ") "" ]; | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   } | 
					
						
							|  |  |  |   :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 ""; | 
					
						
							| 
									
										
										
										
											2023-06-26 11:14:55 +02:00
										 |  |  |   :local Chars ("\n\r !\"#\$%&'()*+,:;<=>?@[\\]^`{|}~"); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   :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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-30 20:58:19 +02:00
										 |  |  |   :set Input [ $CharacterReplace $Input "." "," ]; | 
					
						
							|  |  |  |   :foreach I in={ "alpha"; "beta"; "rc" } do={ | 
					
						
							|  |  |  |     :set Input [ $CharacterReplace $Input $I ("," . $I . ",") ]; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   :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={ | 
					
						
							| 
									
										
										
										
											2023-03-30 20:58:19 +02:00
										 |  |  |         :if ($Value = "alpha") do={ :set Return ($Return + 0x3f00); } | 
					
						
							|  |  |  |         :if ($Value = "beta") do={ :set Return ($Return + 0x5f00); } | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |         :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={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |       $LogPrint error $0 ("Module '" . $ScriptVal->"name" . "' failed to run."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |   } else={ | 
					
						
							| 
									
										
										
										
											2024-03-08 12:45:38 +01:00
										 |  |  |     $LogPrint error $0 ("Module '" . $ScriptVal->"name" . "' failed syntax validation, skipping."); | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-18 12:53:17 +01:00
										 |  |  | # Log success | 
					
						
							|  |  |  | :local Resource [ /system/resource/get ]; | 
					
						
							| 
									
										
										
										
											2024-03-12 15:19:42 +01:00
										 |  |  | $LogPrintOnce info $ScriptName ("Loaded on " . $Resource->"board-name" . \ | 
					
						
							| 
									
										
										
										
											2024-03-08 11:09:22 +01:00
										 |  |  |   " with RouterOS " . $Resource->"version" . "."); | 
					
						
							| 
									
										
										
										
											2024-01-18 12:53:17 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 10:57:42 +01:00
										 |  |  | # signal we are ready | 
					
						
							|  |  |  | :set GlobalFunctionsReady true; |