diff options
Diffstat (limited to 'global-functions')
-rw-r--r-- | global-functions | 1059 |
1 files changed, 14 insertions, 1045 deletions
diff --git a/global-functions b/global-functions index 996a6e1..366ef94 100644 --- a/global-functions +++ b/global-functions @@ -6,708 +6,27 @@ # # requires RouterOS, version=7.7 # -# global functions -# https://git.eworm.de/cgit/routeros-scripts/about/ - -:local 0 "global-functions"; +# WARNING: If you find this stripped version of global-functions +# on your Router something went wrong and migration failed. To +# recover run this function: $RouterOSScriptsRecover # expected configuration version -:global ExpectedConfigVersion 94; - -# global variables not to be changed by user -:global GlobalFunctionsReady false; -:global Identity [ /system/identity/get name ]; +:global ExpectedConfigVersion 95; # global functions -:global CertificateAvailable; -:global CertificateDownload; -:global CertificateNameByCN; -:global CharacterReplace; -:global CleanFilePath; -:global DeviceInfo; -:global Dos2Unix; -:global DownloadPackage; -:global EitherOr; -:global EscapeForRegEx; -:global GetMacVendor; -:global GetRandom20CharAlNum; -:global GetRandom20CharHex; -:global GetRandomNumber; -:global Grep; -:global HexToNum; -:global IfThenElse; -:global IsDefaultRouteReachable; -:global IsDNSResolving; -:global IsFullyConnected; -:global IsMacLocallyAdministered; -:global IsTimeSync; -:global LogPrintExit2; -:global MkDir; -:global NotificationFunctions; -:global ParseKeyValueStore; -:global PrettyPrint; -:global RandomDelay; -:global Read; -:global RequiredRouterOS; -:global ScriptFromTerminal; +:global RouterOSScriptsRecover; :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; - -# check and download required certificate -:set CertificateAvailable do={ - :local CommonName [ :tostr $1 ]; - - :global CertificateDownload; - :global LogPrintExit2; - :global ParseKeyValueStore; - - :if ([ /system/resource/get free-hdd-space ] < 8388608 && \ - [ /certificate/settings/get crl-download ] = true && \ - [ /certificate/settings/get crl-store ] = "system") do={ - $LogPrintExit2 warning $0 ("This system has low free flash space but " . \ - "is configured to download certificate CRLs to system!") false; - } - - :if ([ :len [ /certificate/find where common-name=$CommonName ] ] = 0) do={ - $LogPrintExit2 info $0 ("Certificate with CommonName \"" . $CommonName . "\" not available.") false; - :if ([ $CertificateDownload $CommonName ] = false) do={ - :return false; - } - } - - :local CertVal [ /certificate/get [ find where common-name=$CommonName ] ]; - :while (($CertVal->"akid") != "" && ($CertVal->"akid") != ($CertVal->"skid")) do={ - :if ([ :len [ /certificate/find where skid=($CertVal->"akid") ] ] = 0) do={ - $LogPrintExit2 info $0 ("Certificate chain for \"" . $CommonName . \ - "\" is incomplete, missing \"" . ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") . "\".") false; - :if ([ $CertificateDownload $CommonName ] = false) do={ - :return false; - } - } - :set CertVal [ /certificate/get [ find where skid=($CertVal->"akid") ] ]; - } - :return true; -} - -# download and import certificate -:set CertificateDownload do={ - :local CommonName [ :tostr $1 ]; - - :global ScriptUpdatesBaseUrl; - :global ScriptUpdatesUrlSuffix; - - :global CertificateNameByCN; - :global LogPrintExit2; - :global UrlEncode; - :global WaitForFile; - - $LogPrintExit2 info $0 ("Downloading and importing certificate with " . \ - "CommonName \"" . $CommonName . "\".") false; - :do { - :local LocalFileName ($CommonName . ".pem"); - :local UrlFileName ([ $UrlEncode $CommonName ] . ".pem"); - /tool/fetch check-certificate=yes-without-crl \ - ($ScriptUpdatesBaseUrl . "certs/" . \ - $UrlFileName . $ScriptUpdatesUrlSuffix) \ - dst-path=$LocalFileName as-value; - $WaitForFile $LocalFileName; - /certificate/import file-name=$LocalFileName passphrase="" as-value; - /file/remove $LocalFileName; - - :foreach Cert in=[ /certificate/find where name~("^" . $LocalFileName . "_[0-9]+\$") ] do={ - $CertificateNameByCN [ /certificate/get $Cert common-name ]; - } - } on-error={ - $LogPrintExit2 warning $0 ("Failed importing certificate with " . \ - "CommonName \"" . $CommonName . "\"!") false; - :return false; - } - :return true; -} - -# name a certificate by its common-name -:set CertificateNameByCN do={ - :local CommonName [ :tostr $1 ]; - - :global CharacterReplace; - - :local Cert [ /certificate/find where common-name=$CommonName ]; - /certificate/set $Cert \ - name=[ $CharacterReplace [ $CharacterReplace [ $CharacterReplace $CommonName "'" "-" ] " " "-" ] "---" "-" ]; -} - -# character replace -:set CharacterReplace do={ - :local String [ :tostr $1 ]; - :local ReplaceFrom [ :tostr $2 ]; - :local ReplaceWith [ :tostr $3 ]; - :local Return ""; - - :if ($ReplaceFrom = "") do={ - :return $String; - } - - :while ([ :typeof [ :find $String $ReplaceFrom ] ] != "nil") do={ - :local Pos [ :find $String $ReplaceFrom ]; - :set Return ($Return . [ :pick $String 0 $Pos ] . $ReplaceWith); - :set String [ :pick $String ($Pos + [ :len $ReplaceFrom ]) [ :len $String ] ]; - } - - :return ($Return . $String); -} - -# clean file path -:set CleanFilePath do={ - :local Path [ :tostr $1 ]; - - :global CharacterReplace; - - :while ($Path ~ "//") do={ - :set $Path [ $CharacterReplace $Path "//" "/" ]; - } - :if ([ :pick $Path 0 ] = "/") do={ - :set Path [ :pick $Path 1 [ :len $Path ] ]; - } - :if ([ :pick $Path ([ :len $Path ] - 1) ] = "/") do={ - :set Path [ :pick $Path 0 ([ :len $Path ] - 1) ]; - } - - :return $Path; -} - -# get readable device info -:set DeviceInfo do={ - :global ExpectedConfigVersion; - :global Identity; - - :global IfThenElse; - - :local Resource [ /system/resource/get ]; - :local RouterBoard; - :do { - :set RouterBoard [[ :parse "/system/routerboard/get" ]]; - } on-error={ } - :local License [ /system/license/get ]; - :local Update [ /system/package/update/get ]; - - :return ( \ - "Hostname: " . $Identity . \ - "\nBoard name: " . $Resource->"board-name" . \ - "\nArchitecture: " . $Resource->"architecture-name" . \ - [ $IfThenElse ($RouterBoard->"routerboard" = true) \ - ("\nModel: " . $RouterBoard->"model" . \ - [ $IfThenElse ([ :len ($RouterBoard->"revision") ] > 0) \ - (" " . $RouterBoard->"revision") ] . \ - "\nSerial number: " . $RouterBoard->"serial-number") ] . \ - [ $IfThenElse ([ :len ($License->"level") ] > 0) \ - ("\nLicense: " . $License->"level") ] . \ - "\nRouterOS:" . \ - "\n Channel: " . $Update->"channel" . \ - "\n Installed: " . $Update->"installed-version" . \ - [ $IfThenElse ([ :typeof ($Update->"latest-version") ] != "nothing" && \ - $Update->"installed-version" != $Update->"latest-version") \ - ("\n Available: " . $Update->"latest-version") ] . \ - [ $IfThenElse ($RouterBoard->"routerboard" = true && \ - $RouterBoard->"current-firmware" != $RouterBoard->"upgrade-firmware") \ - ("\n Firmware: " . $RouterBoard->"current-firmware") ] . \ - "\nRouterOS-Scripts:" . \ - "\n Version: " . $ExpectedConfigVersion); -} - -# convert line endings, DOS -> UNIX -:set Dos2Unix do={ - :local Input [ :tostr $1 ]; - - :global CharacterReplace; - - :return [ $CharacterReplace $Input ("\r\n") ("\n") ]; -} - -# download package from upgrade server -:set DownloadPackage do={ - :local PkgName [ :tostr $1 ]; - :local PkgVer [ :tostr $2 ]; - :local PkgArch [ :tostr $3 ]; - :local PkgDir [ :tostr $4 ]; - - :global CertificateAvailable; - :global CleanFilePath; - :global LogPrintExit2; - :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={ - $LogPrintExit2 warning $0 ("Failed creating directory, not downloading package.") false; - :return false; - } - - :if ([ :len [ /file/find where name=$PkgDest type="package" ] ] > 0) do={ - $LogPrintExit2 info $0 ("Package file " . $PkgName . " already exists.") false; - :return true; - } - - :if ([ $CertificateAvailable "R3" ] = false) do={ - $LogPrintExit2 error $0 ("Downloading required certificate failed.") true; - } - - :local Url ("https://upgrade.mikrotik.com/routeros/" . $PkgVer . "/" . $PkgFile); - $LogPrintExit2 info $0 ("Downloading package file '" . $PkgName . "'...") false; - $LogPrintExit2 debug $0 ("... from url: " . $Url) false; - :local Retry 3; - :while ($Retry > 0) do={ - :do { - /tool/fetch check-certificate=yes-without-crl $Url dst-path=$PkgDest; - $WaitForFile $PkgDest; - - :if ([ /file/get [ find where name=$PkgDest ] type ] = "package") do={ - :return true; - } - } on-error={ - $LogPrintExit2 debug $0 ("Downloading package file failed.") false; - } - - /file/remove [ find where name=$PkgDest ]; - :set Retry ($Retry - 1); - } - - $LogPrintExit2 warning $0 ("Downloading package file '" . $PkgName . "' failed.") false; - :return false; -} - -# return either first (if "true") or second -:set EitherOr do={ - :global IfThenElse; - - :if ([ :typeof $1 ] = "num") do={ - :return [ $IfThenElse ($1 != 0) $1 $2 ]; - } - :return [ $IfThenElse ([ :len [ :tostr $1 ] ] > 0) $1 $2 ]; -} - -# escape for regular expression -:set EscapeForRegEx do={ - :local Input [ :tostr $1 ]; - - :if ([ :len $Input ] = 0) do={ - :return ""; - } - - :local Return ""; - :local Chars ("^.[]\$()|*+\?{}\\"); - - :for I from=0 to=([ :len $Input ] - 1) do={ - :local Char [ :pick $Input $I ]; - :if ([ :find $Chars $Char ]) do={ - :set Char ("\\" . $Char); - } - :set Return ($Return . $Char); - } - - :return $Return; -} - -# get MAC vendor -:set GetMacVendor do={ - :local Mac [ :tostr $1 ]; - - :global CertificateAvailable; - :global IsMacLocallyAdministered; - :global LogPrintExit2; - - :if ([ $IsMacLocallyAdministered $Mac ] = true) do={ - :return "locally administered"; - } - - :do { - :if ([ $CertificateAvailable "R3" ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true; - } - :local Vendor ([ /tool/fetch check-certificate=yes-without-crl \ - ("https://api.macvendors.com/" . [ :pick $Mac 0 8 ]) output=user as-value ]->"data"); - :return $Vendor; - } on-error={ - :do { - /tool/fetch check-certificate=yes-without-crl ("https://api.macvendors.com/") \ - output=none as-value; - $LogPrintExit2 debug $0 ("The mac vendor is not known in database.") false; - } on-error={ - $LogPrintExit2 warning $0 ("Failed getting mac vendor.") false; - } - :return "unknown vendor"; - } -} - -# generate random 20 chars 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 ]; - :local Hex "0123456789abcdef0123456789ABCDEF"; - :local Multi 1; - :local Return 0; - - :for I from=([ :len $Input ] - 1) to=0 do={ - :set Return ($Return + (([ :find $Hex [ :pick $Input $I ] ] % 16) * $Multi)); - :set Multi ($Multi * 16); - } - - :return $Return; -} - -# mimic conditional/ternary operator (condition ? consequent : alternative) -:set IfThenElse do={ - :if ([ :tostr $1 ] = "true" || [ :tobool $1 ] = true) do={ - :return $2; - } - :return $3; -} - -# 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={ - :global CharacterReplace; - - :do { - :resolve "low-ttl.eworm.de"; - } on-error={ - :return false; - } - :return true; -} - -# check if system is is fully connected (default route reachable, DNS resolving, time sync) -:set IsFullyConnected do={ - :global IsDefaultRouteReachable; - :global IsDNSResolving; - :global IsTimeSync; - - :if ([ $IsDefaultRouteReachable ] = false) do={ - :return false; - } - :if ([ $IsDNSResolving ] = false) do={ - :return false; - } - :if ([ $IsTimeSync ] = false) do={ - :return false; - } - :return true; -} - -# check if mac address is locally administered -:set IsMacLocallyAdministered do={ - :if ([ :tonum ("0x" . [ :pick $1 0 [ :find $1 ":" ] ]) ] & 2 = 2) do={ - :return true; - } - :return false; -} - -# check if system time is sync -:set IsTimeSync do={ - :global IsTimeSyncCached; - - :global LogPrintExit2; - - :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; - } - :return false; - } - - :if ([ /system/license/get ]->"level" = "free" || \ - [ /system/resource/get ]->"board-name" = "x86") do={ - $LogPrintExit2 debug $0 ("No ntp client configured, relying on RTC for CHR free license and x86.") false; - :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; - } - - $LogPrintExit2 debug $0 ("No time source configured! Returning gracefully...") false; - :return true; -} - -# log and print with same text, optionally exit -:set LogPrintExit2 do={ - :local Severity [ :tostr $1 ]; - :local Name [ :tostr $2 ]; - :local Message [ :tostr $3 ]; - :local Exit [ :tostr $4 ]; - - :global PrintDebug; - :global PrintDebugOverride; - - :global EitherOr; - - :local Debug [ $EitherOr ($PrintDebugOverride->$Name) $PrintDebug ]; - - :local PrintSeverity do={ - :global TerminalColorOutput; - - :if ($TerminalColorOutput != true) do={ - :return $1; - } - - :local Color { debug=96; info=97; warning=93; error=91 }; - :return ("\1B[" . $Color->$1 . "m" . $1 . "\1B[0m"); - } - - :local Log ([ $EitherOr $Name "<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); - } - - :if ($Exit = "true") do={ - :error ("Hard error to exit."); - } -} - -# create directory -:set MkDir do={ - :local Path [ :tostr $1 ]; - :global CharacterReplace; - :global CleanFilePath; - :global GetRandom20CharAlNum; - :global LogPrintExit2; - :global RequiredRouterOS; - :global WaitForFile; - - :set Path [ $CleanFilePath $Path ]; - - :if ($Path = "") do={ - :return true; - } - - :if ([ :len [ /file/find where name=$Path type="directory" ] ] = 1) do={ - :return true; - } - - :local Error false; - :local PathNext ""; - :foreach Dir in=[ :toarray [ $CharacterReplace $Path "/" "," ] ] do={ - :local Continue false; - :set PathNext [ $CleanFilePath ($PathNext . "/" . $Dir) ]; - - :if ([ :len [ /file/find where name=$PathNext !(name="tmpfs") type="directory" ] ] = 1) do={ - :set Continue true; - } - - :if ($Continue = false && $PathNext = "tmpfs") do={ - :if ([ :len [ /disk/find where slot=tmpfs type=tmpfs ] ] = 0) do={ - $LogPrintExit2 info $0 ("Creating disk of type tmpfs.") false; - /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={ - $LogPrintExit2 warning $0 ("Creating disk of type tmpfs failed!") false; - :set Error true; - } - } - :set Continue true; - } - - :if ($Continue = false && [ :len [ /file/find where name=$PathNext ] ] = 1) do={ - $LogPrintExit2 warning $0 ("The path '" . $PathNext . "' exists, but is not a directory.") false; - :return false; - } +# recover from failed migration +:set RouterOSScriptsRecover do={ + :global ScriptInstallUpdate; - :if ($Continue = false) do={ - :local Name ($PathNext . "-" . [ $GetRandom20CharAlNum 6 ]); - :do { - /ip/smb/share/add disabled=yes directory=$PathNext name=$Name; - $WaitForFile $PathNext; - } on-error={ - $LogPrintExit2 warning $0 ("Making directory '" . $PathNext . "' failed!") false; - :set Error true; - } - /ip/smb/share/remove [ find where name=$Name ]; - :if ($Error = true) do={ - :return false; - } - } + :foreach Script in={ "global-config"; "global-functions" } do={ + /system/script/set name=$Script source=([ /tool/fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script . ".rsc") output=user as-value]->"data"); + /system/script/run $Script; } - :return true; -} -# prepare NotificationFunctions array -:if ([ :typeof $NotificationFunctions ] != "array") do={ - :set NotificationFunctions ({}); -} - -# 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={ - :global EitherOr; - :global GetRandomNumber; - - :delay ([ $GetRandomNumber $1 ] . [ $EitherOr $2 "s" ]); -} - -# read input from user -:set Read do={ - :return; -} - -# check for required RouterOS version -:set RequiredRouterOS do={ - :local Caller [ :tostr $1 ]; - :local Required [ :tostr $2 ]; - :local Warn [ :tostr $3 ]; - - :global IfThenElse; - :global LogPrintExit2; - :global VersionToNum; - - :if (!($Required ~ "^\\d+\\.\\d+((beta|rc|\\.)\\d+|)\$")) do={ - $LogPrintExit2 error $0 ("No valid RouterOS version: " . $Required) false; - :return false; - } - - :if ([ $VersionToNum $Required ] > [ $VersionToNum [ /system/package/update/get installed-version ] ]) do={ - :if ($Warn = "true") do={ - $LogPrintExit2 warning $0 ("This " . [ $IfThenElse ([ :pick $Caller 0 ] = ("\$")) "function" "script" ] . \ - " '" . $Caller . "' (at least specific functionality) requires RouterOS " . $Required . ". Please update!") false; - } - :return false; - } - :return true; -} - -# check if script is run from terminal -:set ScriptFromTerminal do={ - :local Script [ :tostr $1 ]; - - :global LogPrintExit2; - - :foreach Job in=[ /system/script/job/find where script=$Script ] do={ - :set Job [ /system/script/job/get $Job ]; - :while ([ :typeof ($Job->"parent") ] = "id") do={ - :set Job [ /system/script/job/get [ find where .id=($Job->"parent") ] ]; - } - :if (($Job->"type") = "login") do={ - $LogPrintExit2 debug $0 ("Script " . $Script . " started from terminal.") false; - :return true; - } - } - $LogPrintExit2 debug $0 ("Script " . $Script . " NOT started from terminal.") false; - - :return false; + $ScriptInstallUpdate; } # install new scripts, update existing scripts @@ -779,7 +98,7 @@ :local UrlSuffix $ScriptUpdatesUrlSuffix; :if ([ :typeof ($Comment->"base-url") ] = "str") do={ :set BaseUrl ($Comment->"base-url"); } :if ([ :typeof ($Comment->"url-suffix") ] = "str") do={ :set UrlSuffix ($Comment->"url-suffix"); } - :local Url ($BaseUrl . $ScriptVal->"name" . $UrlSuffix); + :local Url ($BaseUrl . $ScriptVal->"name" . ".rsc" . $UrlSuffix); $LogPrintExit2 debug $0 ("Fetching script '" . $ScriptVal->"name" . "' from url: " . $Url) false; :local Result [ /tool/fetch check-certificate=yes-without-crl $Url output=user as-value ]; @@ -863,7 +182,7 @@ :local ChangeLogCode; :do { - :local Url ($ScriptUpdatesBaseUrl . "global-config.changes" . $ScriptUpdatesUrlSuffix); + :local Url ($ScriptUpdatesBaseUrl . "news-and-changes.rsc" . $ScriptUpdatesUrlSuffix); $LogPrintExit2 debug $0 ("Fetching news, changes and migration: " . $Url) false; :local Result [ /tool/fetch check-certificate=yes-without-crl $Url output=user as-value ]; :if ($Result->"status" = "finished") do={ @@ -940,353 +259,3 @@ :set GlobalConfigMigration; } } - -# lock script against multiple invocation -:set ScriptLock do={ - :local Script [ :tostr $1 ]; - :local DoReturn $2; - :local WaitMax ([ :tonum $3 ] * 10); - - :global GetRandom20CharAlNum; - :global IfThenElse; - :global LogPrintExit2; - - :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={ - $LogPrintExit2 error $0 ("A script named '" . $Script . "' does not exist!") true; - } - - :if ([ $JobCount $Script ] = 0) do={ - $LogPrintExit2 error $0 ("No script '" . $Script . "' is running!") true; - } - - :if ([ $TicketCount $Script ] >= [ $JobCount $Script ]) do={ - $LogPrintExit2 error $0 ("More tickets than running scripts '" . $Script . "', resetting!") false; - :set ($ScriptLockOrder->$Script) ({}); - /system/script/job/remove [ find where script=$Script ]; - } - - :local MyTicket [ $GetRandom20CharAlNum 6 ]; - $AddTicket $Script $MyTicket; - - :local WaitCount 0; - :while ($WaitMax > $WaitCount && ([ $IsFirstTicket $Script $MyTicket ] = false || [ $TicketCount $Script ] < [ $JobCount $Script ])) do={ - :set WaitCount ($WaitCount + 1); - :delay 100ms; - } - - :if ([ $IsFirstTicket $Script $MyTicket ] = true && [ $TicketCount $Script ] = [ $JobCount $Script ]) do={ - $RemoveTicket $Script $MyTicket; - $CleanupTickets $Script; - :return false; - } - - $RemoveTicket $Script $MyTicket; - $LogPrintExit2 info $0 ("Script '" . $Script . "' started more than once" . [ $IfThenElse ($WaitCount > 0) \ - " and timed out waiting for lock" "" ] . "... Aborting.") [ $IfThenElse ($DoReturn = true) false true ]; - :return true; -} - -# send notification via NotificationFunctions - expects at least two string arguments -:set SendNotification do={ - :global SendNotification2; - - $SendNotification2 ({ subject=$1; message=$2; link=$3; silent=$4 }); -} - -# send notification via NotificationFunctions - expects one array argument -:set SendNotification2 do={ - :local Notification $1; - - :global NotificationFunctions; - - :foreach FunctionName,Discard in=$NotificationFunctions do={ - ($NotificationFunctions->$FunctionName) \ - ("\$NotificationFunctions->\"" . $FunctionName . "\"") \ - $Notification; - } -} - -# return UTF-8 symbol for unicode name -:set SymbolByUnicodeName do={ - :local Symbols { - "abacus"="\F0\9F\A7\AE"; - "alarm-clock"="\E2\8F\B0"; - "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"; - "high-voltage-sign"="\E2\9A\A1"; - "incoming-envelope"="\F0\9F\93\A8"; - "link"="\F0\9F\94\97"; - "lock-with-ink-pen"="\F0\9F\94\8F"; - "memo"="\F0\9F\93\9D"; - "mobile-phone"="\F0\9F\93\B1"; - "pushpin"="\F0\9F\93\8C"; - "scissors"="\E2\9C\82"; - "sparkles"="\E2\9C\A8"; - "speech-balloon"="\F0\9F\92\AC"; - "up-arrow"="\E2\AC\86"; - "warning-sign"="\E2\9A\A0"; - "white-heavy-check-mark"="\E2\9C\85" - } - - :return (($Symbols->$1) . "\EF\B8\8F"); -} - -# return symbol for notification -:set SymbolForNotification do={ - :global NotificationsWithSymbols; - :global SymbolByUnicodeName; - - :if ($NotificationsWithSymbols != true) do={ - :return ""; - } - :local Return ""; - :foreach Symbol in=[ :toarray $1 ] do={ - :set Return ($Return . [ $SymbolByUnicodeName $Symbol ]); - } - :return ($Return . " "); -} - -# convert line endings, UNIX -> DOS -:set Unix2Dos do={ - :local Input [ :tostr $1 ]; - - :global CharacterReplace; - - :return [ $CharacterReplace [ $CharacterReplace $Input \ - ("\n") ("\r\n") ] ("\r\r\n") ("\r\n") ]; -} - -# url encoding -:set UrlEncode do={ - :local Input [ :tostr $1 ]; - - :if ([ :len $Input ] = 0) do={ - :return ""; - } - - :local Return ""; - :local Chars ("\n\r !\"#\$%&'()*+,:;<=>\?@[\\]^`{|}~"); - :local Subs { "%0A"; "%0D"; "%20"; "%21"; "%22"; "%23"; "%24"; "%25"; "%26"; "%27"; - "%28"; "%29"; "%2A"; "%2B"; "%2C"; "%3A"; "%3B"; "%3C"; "%3D"; "%3E"; "%3F"; - "%40"; "%5B"; "%5C"; "%5D"; "%5E"; "%60"; "%7B"; "%7C"; "%7D"; "%7E" }; - - :for I from=0 to=([ :len $Input ] - 1) do={ - :local Char [ :pick $Input $I ]; - :local Replace [ :find $Chars $Char ]; - - :if ([ :typeof $Replace ] = "num") do={ - :set Char ($Subs->$Replace); - } - :set Return ($Return . $Char); - } - - :return $Return; -} - -# basic syntax validation -:set ValidateSyntax do={ - :local Code [ :tostr $1 ]; - - :do { - [ :parse (":local Validate do={\n" . $Code . "\n}") ]; - } on-error={ - :return false; - } - :return true; -} - -# convert version string to numeric value -:set VersionToNum do={ - :local Input [ :tostr $1 ]; - :local Multi 0x1000000; - :local Return 0; - - :global CharacterReplace; - - :set Input [ $CharacterReplace [ $CharacterReplace [ $CharacterReplace $Input \ - "." "," ] "beta" ",beta," ] "rc" ",rc," ]; - - :foreach Value in=([ :toarray $Input ], 0) do={ - :local Num [ :tonum $Value ]; - :if ($Multi = 0x100) do={ - :if ([ :typeof $Num ] = "num") do={ - :set Return ($Return + 0xff00); - :set Multi ($Multi / 0x100); - } else={ - :if ($Value = "beta") do={ :set Return ($Return + 0x3f00); } - :if ($Value = "rc") do={ :set Return ($Return + 0x7f00); } - } - } - :if ([ :typeof $Num ] = "num") do={ :set Return ($Return + ($Value * $Multi)); } - :set Multi ($Multi / 0x100); - } - - :return $Return; -} - -# wait for default route to be reachable -:set WaitDefaultRouteReachable do={ - :global 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={ - $LogPrintExit2 error $0 ("Module '" . $ScriptVal->"name" . "' failed to run.") false; - } - } else={ - $LogPrintExit2 error $0 ("Module '" . $ScriptVal->"name" . "' failed syntax validation, skipping.") false; - } -} - -# signal we are ready -:set GlobalFunctionsReady true; |