diff --git a/CONTRIBUTIONS.md b/CONTRIBUTIONS.md index 00861c18..5bf5d089 100644 --- a/CONTRIBUTIONS.md +++ b/CONTRIBUTIONS.md @@ -35,7 +35,6 @@ Add yourself to the list, [donate with PayPal ↗️](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)! * Abdul Mannan Abbasi -* Alex Maier * Andrea Ruffini Perico * Andrew Cox * Christoph Boss (@Kampfwurst) diff --git a/INITIAL-COMMANDS.md b/INITIAL-COMMANDS.md index 40f609b9..79773bd9 100644 --- a/INITIAL-COMMANDS.md +++ b/INITIAL-COMMANDS.md @@ -18,21 +18,17 @@ Run the complete base installation: { :local BaseUrl "https://git.eworm.de/cgit/routeros-scripts/plain/"; - :local CertCommonName "ISRG Root X2"; :local CertFileName "ISRG-Root-X2.pem"; :local CertFingerprint "69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470"; - :if (!(([ /certificate/settings/get ]->"builtin-trust-anchors") = "trusted" && \ - [[ :parse (":return [ :len [ /certificate/builtin/find where common-name=\"" . $CertCommonName . "\" ] ]") ]] > 0)) do={ - :put "Importing certificate..."; - /tool/fetch ($BaseUrl . "certs/" . $CertFileName) dst-path=$CertFileName as-value; - :delay 1s; - /certificate/import file-name=$CertFileName passphrase=""; - :if ([ :len [ /certificate/find where fingerprint=$CertFingerprint ] ] != 1) do={ - :error "Something is wrong with your certificates!"; - }; - :delay 1s; + :put "Importing certificate..."; + /tool/fetch ($BaseUrl . "certs/" . $CertFileName) dst-path=$CertFileName as-value; + :delay 1s; + /certificate/import file-name=$CertFileName passphrase=""; + :if ([ :len [ /certificate/find where fingerprint=$CertFingerprint ] ] != 1) do={ + :error "Something is wrong with your certificates!"; }; + :delay 1s; :put "Renaming global-config-overlay, if exists..."; /system/script/set name=("global-config-overlay-" . [ /system/clock/get date ] . "-" . [ /system/clock/get time ]) [ find where name="global-config-overlay" ]; :foreach Script in={ "global-config"; "global-config-overlay"; "global-functions" } do={ @@ -45,11 +41,9 @@ Run the complete base installation: :put "Scheduling to load configuration and functions..."; /system/scheduler/remove [ find where name="global-scripts" ]; /system/scheduler/add name="global-scripts" start-time=startup on-event="/system/script { run global-config; run global-functions; }"; - :if ([ :len [ /certificate/find where fingerprint=$CertFingerprint ] ] > 0) do={ - :put "Renaming certificate by its common-name..."; - :global CertificateNameByCN; - $CertificateNameByCN $CertFingerprint; - }; + :put "Renaming certificate by its common-name..."; + :global CertificateNameByCN; + $CertificateNameByCN $CertFingerprint; }; Then continue setup with diff --git a/README.md b/README.md index 243e1fc5..2a8b2cef 100644 --- a/README.md +++ b/README.md @@ -72,15 +72,7 @@ including demonstation recorded live at [MUM Europe ### The long way in detail The update script does server certificate verification, so first step is to -download the certificates. - -> 💡️ **Hint**: RouterOS 7.19 comes with a builtin certificate store. You -> can skip the steps regarding certificate download and import and jump -> to [installation of scripts](#installation-of-scripts) if you set the -> trust for these builtin trust anchors: -> `/certificate/settings/set builtin-trust-anchors=trusted;` - -If you intend to download the scripts from a +download the certificates. If you intend to download the scripts from a different location (for example from github.com) install the corresponding certificate chain. @@ -114,8 +106,6 @@ is shown. Always make sure there are no certificates installed you do not know or want! -#### Installation of scripts - All following commands will verify the server certificate. For validity the certificate's lifetime is checked with local time, so make sure the device's date and time is set correctly! diff --git a/backup-email.rsc b/backup-email.rsc index 8015beaa..632d2e60 100644 --- a/backup-email.rsc +++ b/backup-email.rsc @@ -27,7 +27,6 @@ :global CleanName; :global DeviceInfo; - :global FileExists; :global FormatLine; :global LogPrint; :global MkDir; @@ -125,19 +124,17 @@ attach=$Attach; remove-attach=true }); # wait for the mail to be sent - :do { - :retry { - :if ([ $FileExists ($FilePath . ".conf") ".conf file" ] = true || \ - [ $FileExists ($FilePath . ".backup") "backup" ] = true || \ - [ $FileExists ($FilePath . ".rsc") "script" ] = true) do={ - :error "Files are still available."; - } - } delay=1s max=120; - } on-error={ - $LogPrint warning $ScriptName ("Files are still available, sending e-mail failed."); - :set PackagesUpdateBackupFailure true; + :local I 0; + :while ([ :len [ /file/find where name ~ ($FilePath . "\\.(backup|rsc)\$") ] ] > 0) do={ + :if ($I >= 120) do={ + $LogPrint warning $ScriptName ("Files are still available, sending e-mail failed."); + :set PackagesUpdateBackupFailure true; + :set ExitOK true; + :error false; + } + :delay 1s; + :set I ($I + 1); } - # do not remove the files here, as the mail is still queued! } do={ :global ExitError; $ExitError $ExitOK [ :jobname ] $Err; } diff --git a/capsman-download-packages.capsman.rsc b/capsman-download-packages.capsman.rsc index aaebf5c0..cab1e4c3 100644 --- a/capsman-download-packages.capsman.rsc +++ b/capsman-download-packages.capsman.rsc @@ -4,7 +4,7 @@ # Michael Gisbers # https://rsc.eworm.de/COPYING.md # -# requires RouterOS, version=7.18 +# requires RouterOS, version=7.15 # # download and cleanup packages for CAP installation from CAPsMAN # https://rsc.eworm.de/doc/capsman-download-packages.md @@ -20,7 +20,6 @@ :global CleanFilePath; :global DownloadPackage; - :global FileGet; :global LogPrint; :global MkDir; :global RmFile; @@ -43,7 +42,7 @@ :error false; } - :if ([ $FileGet $PackagePath ] = false) do={ + :if ([ :len [ /file/find where name=$PackagePath type="directory" ] ] = 0) do={ :if ([ $MkDir $PackagePath ] = false) do={ $LogPrint warning $ScriptName ("Creating directory at CAPsMAN package path (" . \ $PackagePath . ") failed!"); @@ -54,8 +53,8 @@ "). Please place your packages!"); } - :foreach Package in=[ /file/find recursive where path=$PackagePath \ - type="package" package-version!=$InstalledVersion ] do={ + :foreach Package in=[ /file/find where type=package \ + package-version!=$InstalledVersion name~("^" . $PackagePath) ] do={ :local File [ /file/get $Package ]; :if ($File->"package-architecture" = "mips") do={ :set ($File->"package-architecture") "mipsbe"; @@ -67,7 +66,7 @@ } } - :if ([ :len [ /file/find recursive where path=$PackagePath type="package" ] ] = 0) do={ + :if ([ :len [ /file/find where type=package name~("^" . $PackagePath) ] ] = 0) do={ $LogPrint info $ScriptName ("No packages available, downloading default set."); :foreach Arch in={ "arm"; "mipsbe" } do={ :foreach Package in={ "routeros"; "wireless" } do={ diff --git a/capsman-download-packages.template.rsc b/capsman-download-packages.template.rsc index ebbba709..ea411201 100644 --- a/capsman-download-packages.template.rsc +++ b/capsman-download-packages.template.rsc @@ -4,7 +4,7 @@ # Michael Gisbers # https://rsc.eworm.de/COPYING.md # -# requires RouterOS, version=7.18 +# requires RouterOS, version=7.15 # # download and cleanup packages for CAP installation from CAPsMAN # https://rsc.eworm.de/doc/capsman-download-packages.md @@ -21,7 +21,6 @@ :global CleanFilePath; :global DownloadPackage; - :global FileGet; :global LogPrint; :global MkDir; :global RmFile; @@ -45,7 +44,7 @@ :error false; } - :if ([ $FileGet $PackagePath ] = false) do={ + :if ([ :len [ /file/find where name=$PackagePath type="directory" ] ] = 0) do={ :if ([ $MkDir $PackagePath ] = false) do={ $LogPrint warning $ScriptName ("Creating directory at CAPsMAN package path (" . \ $PackagePath . ") failed!"); @@ -56,8 +55,8 @@ "). Please place your packages!"); } - :foreach Package in=[ /file/find recursive where path=$PackagePath \ - type="package" package-version!=$InstalledVersion ] do={ + :foreach Package in=[ /file/find where type=package \ + package-version!=$InstalledVersion name~("^" . $PackagePath) ] do={ :local File [ /file/get $Package ]; :if ($File->"package-architecture" = "mips") do={ :set ($File->"package-architecture") "mipsbe"; @@ -69,7 +68,7 @@ } } - :if ([ :len [ /file/find recursive where path=$PackagePath type="package" ] ] = 0) do={ + :if ([ :len [ /file/find where type=package name~("^" . $PackagePath) ] ] = 0) do={ $LogPrint info $ScriptName ("No packages available, downloading default set."); # NOT /interface/wifi/ # :foreach Arch in={ "arm"; "mipsbe" } do={ diff --git a/capsman-download-packages.wifi.rsc b/capsman-download-packages.wifi.rsc index 7de0431f..a8103569 100644 --- a/capsman-download-packages.wifi.rsc +++ b/capsman-download-packages.wifi.rsc @@ -4,7 +4,7 @@ # Michael Gisbers # https://rsc.eworm.de/COPYING.md # -# requires RouterOS, version=7.18 +# requires RouterOS, version=7.15 # # download and cleanup packages for CAP installation from CAPsMAN # https://rsc.eworm.de/doc/capsman-download-packages.md @@ -20,7 +20,6 @@ :global CleanFilePath; :global DownloadPackage; - :global FileGet; :global LogPrint; :global MkDir; :global RmFile; @@ -43,7 +42,7 @@ :error false; } - :if ([ $FileGet $PackagePath ] = false) do={ + :if ([ :len [ /file/find where name=$PackagePath type="directory" ] ] = 0) do={ :if ([ $MkDir $PackagePath ] = false) do={ $LogPrint warning $ScriptName ("Creating directory at CAPsMAN package path (" . \ $PackagePath . ") failed!"); @@ -54,8 +53,8 @@ "). Please place your packages!"); } - :foreach Package in=[ /file/find recursive where path=$PackagePath \ - type="package" package-version!=$InstalledVersion ] do={ + :foreach Package in=[ /file/find where type=package \ + package-version!=$InstalledVersion name~("^" . $PackagePath) ] do={ :local File [ /file/get $Package ]; :if ($File->"package-architecture" = "mips") do={ :set ($File->"package-architecture") "mipsbe"; @@ -67,7 +66,7 @@ } } - :if ([ :len [ /file/find recursive where path=$PackagePath type="package" ] ] = 0) do={ + :if ([ :len [ /file/find where type=package name~("^" . $PackagePath) ] ] = 0) do={ $LogPrint info $ScriptName ("No packages available, downloading default set."); :foreach Arch in={ "arm"; "arm64" } do={ :local Packages { "arm"={ "routeros"; "wifi-qcom"; "wifi-qcom-ac" }; diff --git a/check-routeros-update.rsc b/check-routeros-update.rsc index 8b80ddeb..e28a0190 100644 --- a/check-routeros-update.rsc +++ b/check-routeros-update.rsc @@ -28,7 +28,6 @@ :global EscapeForRegEx; :global FetchUserAgentStr; :global LogPrint; - :global RebootForUpdate; :global ScriptFromTerminal; :global ScriptLock; :global SendNotification2; @@ -63,14 +62,9 @@ $WaitFullyConnected; :if ([ :len [ /system/scheduler/find where name="_RebootForUpdate" ] ] > 0) do={ - :if ([ :typeof $RebootForUpdate ] = "nothing") do={ - $LogPrint info $ScriptName ("Found a stale scheduler for reboot, removing."); - /system/scheduler/remove "_RebootForUpdate"; - } else={ - $LogPrint info $ScriptName ("A reboot for update is already scheduled."); - :set ExitOK true; - :error false; - } + $LogPrint info $ScriptName ("A reboot for update is already scheduled."); + :set ExitOK true; + :error false; } $LogPrint debug $ScriptName ("Checking for updates..."); diff --git a/doc/capsman-download-packages.md b/doc/capsman-download-packages.md index 4daad494..57222272 100644 --- a/doc/capsman-download-packages.md +++ b/doc/capsman-download-packages.md @@ -4,7 +4,7 @@ Download packages for CAP upgrade from CAPsMAN [![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) [![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) [![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.18-yellow?style=flat)](https://mikrotik.com/download/changelogs/) +[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) [![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) [![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) diff --git a/doc/packages-update.md b/doc/packages-update.md index a0a17956..75225fe2 100644 --- a/doc/packages-update.md +++ b/doc/packages-update.md @@ -46,8 +46,8 @@ Configuration The configuration goes to `global-config-overlay`, this is the only parameter: -* `PackagesUpdateDeferReboot`: defer the reboot for night (between 3 AM and - 5 AM), use a numerical value in days suffixed with a `d` to defer further +* `PackagesUpdateDeferReboot`: defer the reboot for night (between 3 AM + and 5 AM) By modifying the scheduler's `start-time` you can force the reboot at different time. diff --git a/global-functions.rsc b/global-functions.rsc index 50426b69..55dbdee9 100644 --- a/global-functions.rsc +++ b/global-functions.rsc @@ -38,8 +38,6 @@ :global ExitError; :global FetchHuge; :global FetchUserAgentStr; -:global FileExists; -:global FileGet; :global FormatLine; :global FormatMultiLines; :global GetMacVendor; @@ -121,11 +119,6 @@ :return false; } - :if (([ /certificate/settings/get ]->"builtin-trust-anchors") = "trusted" && \ - [[ :parse (":return [ :len [ /certificate/builtin/find where common-name=\"" . $CommonName . "\" ] ]") ]] > 0) do={ - :return true; - } - :if ([ :len [ /certificate/find where common-name=$CommonName ] ] = 0) do={ $LogPrint info $0 ("Certificate with CommonName '" . $CommonName . "' not available."); :if ([ $CertificateDownload $CommonName ] = false) do={ @@ -365,7 +358,6 @@ :global CertificateAvailable; :global CleanFilePath; - :global FileExists; :global LogPrint; :global MkDir; :global RmFile; @@ -386,7 +378,7 @@ :return false; } - :if ([ $FileExists $PkgDest "package" ] = true) do={ + :if ([ :len [ /file/find where name=$PkgDest type="package" ] ] > 0) do={ $LogPrint info $0 ("Package file " . $PkgName . " already exists."); :return true; } @@ -408,7 +400,7 @@ :return false; } - :if ([ $FileExists $PkgDest "package" ] = false) do={ + :if ([ /file/get [ find where name=$PkgDest ] type ] != "package") do={ $LogPrint warning $0 ("Downloaded file is not a package, removing."); $RmFile $PkgDest; :return false; @@ -532,41 +524,6 @@ $Resource->"architecture-name" . " " . $Caller . "/Fetch (https://rsc.eworm.de/)"); } -# check for existence of file, optionally with type -:set FileExists do={ - :local FileName [ :tostr $1 ]; - :local Type [ :tostr $2 ]; - - :global FileGet; - - :local FileVal [ $FileGet $FileName ]; - :if ($FileVal = false) do={ - :return false; - } - - :if ([ :len ($FileVal->"size") ] = 0) do={ - :return false; - } - - :if ([ :len $Type ] = 0 || $FileVal->"type" = $Type) do={ - :return true; - } - - :return false; -} - -# get file properties in array, or false on error -:set FileGet do={ - :local FileName [ :tostr $1 ]; - - :local FileVal false; - :do { - :set FileVal [ /file/get $FileName ]; - } on-error={ } - - :return $FileVal; -} - # format a line for output :set FormatLine do={ :local Key [ :tostr $1 ]; @@ -918,7 +875,6 @@ :local Path [ :tostr $1 ]; :global CleanFilePath; - :global FileGet; :global LogPrint; :global RmDir; :global WaitForFile; @@ -956,8 +912,7 @@ $LogPrint debug $0 ("Making directory: " . $Path); - :local PathVal [ $FileGet $Path ]; - :if ($PathVal->"type" = "directory") do={ + :if ([ :len [ /file/find where name=$Path type="directory" ] ] = 1) do={ $LogPrint debug $0 ("... which already exists."); :return true; } @@ -1082,26 +1037,25 @@ :set RmDir do={ :local DirName [ :tostr $1 ]; - :global FileGet; :global LogPrint; $LogPrint debug $0 ("Removing directory: ". $DirName); - :local DirVal [ $FileGet $DirName ]; - :if ($DirVal = false) do={ - $LogPrint debug $0 ("... which does not exist."); - :return true; - } - - :if ($DirVal->"type" != "directory") do={ + :if ([ :len [ /file/find where name=$DirName type!=directory ] ] > 0) do={ $LogPrint error $0 ("Directory '" . $DirName . "' is not a directory."); :return false; } + :local Dir [ /file/find where name=$DirName type=directory ]; + :if ([ :len $Dir ] = 0) do={ + $LogPrint debug $0 ("... which does not exist."); + :return true; + } + :onerror Err { - /file/remove $DirName; + /file/remove $Dir; } do={ - $LogPrint error $0 ("Removing directory '" . $DirName . "' failed: " . $Err); + $LogPrint error $0 ("Removing directory '" . $DirName . "' (" . $Dir . ") failed: " . $Err); :return false; } :return true; @@ -1111,26 +1065,25 @@ :set RmFile do={ :local FileName [ :tostr $1 ]; - :global FileGet; :global LogPrint; $LogPrint debug $0 ("Removing file: ". $FileName); - :local FileVal [ $FileGet $FileName ]; - :if ($FileVal = false) do={ - $LogPrint debug $0 ("... which does not exist."); - :return true; - } - - :if ($FileVal->"type" = "directory" || $FileVal->"type" = "disk") do={ + :if ([ :len [ /file/find where name=$FileName (type=directory or type=disk) ] ] > 0) do={ $LogPrint error $0 ("File '" . $FileName . "' is not a file."); :return false; } + :local File [ /file/find where name=$FileName !(type=directory or type=disk) ]; + :if ([ :len $File ] = 0) do={ + $LogPrint debug $0 ("... which does not exist."); + :return true; + } + :onerror Err { - /file/remove $FileName; + /file/remove $File; } do={ - $LogPrint error $0 ("Removing file '" . $FileName . "' failed: " . $Err); + $LogPrint error $0 ("Removing file '" . $FileName . "' (" . $File . ") failed: " . $Err); :return false; } :return true; @@ -1771,14 +1724,25 @@ :global MAX; :set FileName [ $CleanFilePath $FileName ]; + :local I 1; :local Delay ([ $MAX [ $EitherOr $WaitTime 2s ] 100ms ] / 10); - :do { - :retry { + :while ([ :len [ /file/find where name=$FileName ] ] = 0) do={ + :if ($I >= 10) do={ + :return false; + } + :delay $Delay; + :set I ($I + 1); + } + + :while ([ :len [ /file/find where name=$FileName ] ] > 0) do={ + :do { /file/get $FileName; :return true; - } delay=$Delay max=10; - } on-error={ } + } on-error={ } + :delay $Delay; + :set Delay ($Delay * 3 / 2); + } :return false; } diff --git a/mod/notification-email.rsc b/mod/notification-email.rsc index ad9762a3..52937668 100644 --- a/mod/notification-email.rsc +++ b/mod/notification-email.rsc @@ -40,11 +40,9 @@ :global EitherOr; :global EMailGenerateFrom; - :global FileExists; :global IsDNSResolving; :global IsTimeSync; :global LogPrint; - :global RmFile; :local AllDone true; :local QueueLen [ :len $EmailQueue ]; @@ -95,7 +93,7 @@ :onerror Err { :local Attach ({}); :foreach File in=[ :toarray [ $EitherOr ($Message->"attach") "" ] ] do={ - :if ([ $FileExists $File ] = true) do={ + :if ([ :len [ /file/find where name=$File ] ] = 1) do={ :set Attach ($Attach, $File); } else={ $LogPrint warning $0 ("File '" . $File . "' does not exist, can not attach."); @@ -112,7 +110,7 @@ :set Wait false; :if (($Message->"remove-attach") = true) do={ :foreach File in=$Attach do={ - $RmFile $File; + /file/remove $File; } } } diff --git a/mod/ssh-keys-import.rsc b/mod/ssh-keys-import.rsc index 7bdc95da..94675255 100644 --- a/mod/ssh-keys-import.rsc +++ b/mod/ssh-keys-import.rsc @@ -75,7 +75,6 @@ :local User [ :tostr $2 ]; :global EitherOr; - :global FileExists; :global LogPrint; :global ParseKeyValueStore; :global SSHKeysImport; @@ -85,7 +84,8 @@ :return false; } - :if ([ $FileExists $FileName ] = true) do={ + :local File [ /file/find where name=$FileName ]; + :if ([ :len $File ] = 0) do={ $LogPrint warning $0 ("File '" . $FileName . "' does not exist."); :return false; } diff --git a/packages-update.rsc b/packages-update.rsc index 43f67494..4fde131b 100644 --- a/packages-update.rsc +++ b/packages-update.rsc @@ -31,24 +31,19 @@ :local Schedule do={ :local ScriptName [ :tostr $1 ]; - :global PackagesUpdateDeferReboot; - :global GetRandomNumber; - :global IfThenElse; :global LogPrint; :global RebootForUpdate do={ /system/reboot; } - :local Interval [ $IfThenElse ($PackagesUpdateDeferReboot >= 1d) $PackagesUpdateDeferReboot 1d ]; :local StartTime [ :tostr [ :totime (10800 + [ $GetRandomNumber 7200 ]) ] ]; - /system/scheduler/add name="_RebootForUpdate" start-time=$StartTime interval=$Interval \ + /system/scheduler/add name="_RebootForUpdate" start-time=$StartTime interval=1d \ on-event=("/system/scheduler/remove \"_RebootForUpdate\"; " . \ ":global RebootForUpdate; \$RebootForUpdate;"); $LogPrint info $ScriptName ("Scheduled reboot for update at " . $StartTime . \ - " local time (" . [ /system/clock/get time-zone-name ] . ")" . \ - [ $IfThenElse ($Interval > 1d) (" deferred by " . $Interval) ] . "."); + " local time (" . [ /system/clock/get time-zone-name ] . ")."); :return true; } @@ -158,7 +153,7 @@ :error true; } } else={ - :if ($PackagesUpdateDeferReboot = true || $PackagesUpdateDeferReboot >= 1d) do={ + :if ($PackagesUpdateDeferReboot = true) do={ $Schedule $ScriptName; :set ExitOK true; :error true; diff --git a/telegram-chat.rsc b/telegram-chat.rsc index 7f7b7a79..fdd08838 100644 --- a/telegram-chat.rsc +++ b/telegram-chat.rsc @@ -30,7 +30,6 @@ :global CertificateAvailable; :global EitherOr; :global EscapeForRegEx; - :global FileExists; :global GetRandom20CharAlNum; :global IfThenElse; :global LogPrint; @@ -155,7 +154,7 @@ :if ([ $WaitForFile ($File . ".done") [ $EitherOr $TelegramChatRunTime 20s ] ] = false) do={ :set State ([ $SymbolForNotification "warning-sign" ] . "The command did not finish, still running in background.\n\n"); } - :if ([ $FileExists ($File . ".failed") ] = true) do={ + :if ([ :len [ /file/find where name=($File . ".failed") ] ] > 0) do={ :set State ([ $SymbolForNotification "cross-mark" ] . "The command failed with an error!\n\n"); } :local Content ([ /file/read chunk-size=32768 file=$File as-value ]->"data");