From 0b2bed1a32d48d3d34e1450066bcf9dda70347b6 Mon Sep 17 00:00:00 2001 From: vados-dev Date: Wed, 15 Jul 2026 09:40:03 +0300 Subject: [PATCH] Backup for MikroNUC created successfully at Jul 15 09:40:03. --- .../20260715/20260715094001-Binary.backup | Bin 0 -> 385344 bytes MikroNUC/20260715/20260715094001-Config.rsc | 5520 +++++++++++++++++ .../20260715/20260715094001-Diff-Config.rsc | 6 + 3 files changed, 5526 insertions(+) create mode 100644 MikroNUC/20260715/20260715094001-Binary.backup create mode 100644 MikroNUC/20260715/20260715094001-Config.rsc create mode 100644 MikroNUC/history/20260715/20260715094001-Diff-Config.rsc diff --git a/MikroNUC/20260715/20260715094001-Binary.backup b/MikroNUC/20260715/20260715094001-Binary.backup new file mode 100644 index 0000000000000000000000000000000000000000..77f4d4bd6ba474bd99d73ba8bdc45403c9a44274 GIT binary patch literal 385344 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 literal 0 HcmV?d00001 diff --git a/MikroNUC/20260715/20260715094001-Config.rsc b/MikroNUC/20260715/20260715094001-Config.rsc new file mode 100644 index 0000000..fb1e5ba --- /dev/null +++ b/MikroNUC/20260715/20260715094001-Config.rsc @@ -0,0 +1,5520 @@ +# 2026-07-15 09:40:17 by RouterOS 7.23.2 +# system id = NmJcEM+8XAP +# +/caps-man channel +add band=2ghz-g/n extension-channel=disabled frequency=2422 name=2G-3 \ + reselect-interval=20m save-selected=yes +add band=5ghz-a/n/ac extension-channel=XXXX frequency=5660 name=5G-5660 \ + save-selected=yes +add band=5ghz-a/n/ac extension-channel=XXXXXXXX frequency=5160,5200,5280 \ + name=5G-5160-5280 reselect-interval=30m save-selected=yes +add band=5ghz-n/ac extension-channel=XXXX frequency=5180 name=5G-36 \ + reselect-interval=30m save-selected=yes +add band=5ghz-n/ac extension-channel=XXXX frequency=5200 name=5G-40 \ + reselect-interval=30m save-selected=yes +add band=5ghz-n/ac extension-channel=XXXX frequency=5220 name=5G-44 \ + reselect-interval=30m save-selected=yes +add band=5ghz-n/ac extension-channel=XXXX frequency=5240 name=5G-48 \ + reselect-interval=30m save-selected=yes +add band=2ghz-g/n extension-channel=disabled frequency=2442 name=2G-7 \ + reselect-interval=20m save-selected=yes +add band=2ghz-g/n extension-channel=disabled frequency=2462 name=2G-11 \ + reselect-interval=20m save-selected=yes +/interface bridge +add name=Bridge-LAN +add name=bridge1 +/interface ethernet +set [ find default-name=ether1 ] disable-running-check=no +/interface wireguard +add comment="Interface for Master Connection" disabled=yes listen-port=22022 \ + mtu=1360 name=MASTER +add listen-port=21641 mtu=1420 name=wg-VDSina +/caps-man security +add authentication-types=wpa2-psk encryption=aes-ccm group-encryption=aes-ccm \ + group-key-update=30m name=xs-Sec +add authentication-types=wpa2-psk encryption=aes-ccm group-encryption=aes-ccm \ + group-key-update=30m name=WPA2 +/disk +add slot=ram tmpfs-max-size=100000000 type=tmpfs +/interface list +add include=dynamic,static name=LAN +add exclude=LAN name=WAN +add exclude=LAN include=WAN name=Internet +/interface wireless security-profiles +set [ find default=yes ] supplicant-identity=MikroMIC +/iot wiliot servers +set *1 address=mqtt.us-east-2.prod.wiliot.cloud name="Wiliot US East" +/ip dhcp-server option +add code=12 name=HostName +add code=119 force=yes name=domain-search-list value="0x03'WORKGROUP'0x00" +/ip dhcp-server option sets +add name=P1505n options=HostName +add name=StartLife1 options=HostName +add name=amsterDev options=HostName +add name=MiTV options=HostName +add name=miller options=HostName +add name=amster options=HostName +add name=garage options=HostName +add name=domain-search-set options=domain-search-list +/ip pool +add name=dhcp ranges=10.30.30.150-10.30.30.200 +/system logging action +add email-start-tls=yes email-to=vados@vados.ru name=email target=email +/system script +add dont-require-permissions=no name=test owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + \r\ + \n#:local ScriptName [ :jobname ];\r\ + \n:local expSrc;\r\ + \n:local FileName;\r\ + \n:local FilePath;\r\ + \n:local DirPath \"tmpfs/\";\r\ + \n:global WaitForFile;\r\ + \n:global IfThenElse;\r\ + \n\r\ + \n:foreach Script in=([ /system/script find where name~\"Amster-GlobalConf\ + *\" ]) do={\r\ + \n:local ScriptVal [ /system/script/get \$Script ];\r\ + \n\r\ + \n:local expName [ (\$ScriptVal->\"name\") ];\r\ + \n#[/system/script get \$expScript name];\r\ + \n:delay 2s;\r\ + \n\r\ + \n:set expSrc [/system/script print where name=\$expName];\r\ + \n/system/script print where name=\$expName;\r\ + \n#[:put [/system/script get \$expScript source]];\r\ + \n:put [\$expSrc];\r\ + \n:delay 2s;\r\ + \n:set FileName \$expName;\r\ + \n:set FilePath (\$DirPath . \$FileName);\r\ + \n\r\ + \n#:log warning \"\\n----------------------\\n\\\$expSrc = \$expSrc\\n____\ + ______________________________\";\r\ + \n:log info \"\\n\\\$FileName = '\$FileName'\";\r\ + \n:log info \"\\n\\\$FilePath = \$FilePath\";\r\ + \n#:log warning {/system/script/get source=[/system/script/get \$expScript\ + \_source];};\r\ + \n\r\ + \n#\$[/system script get [/system script find name=\"\$FileName\"] source]\ + \r\ + \n\r\ + \n#:local gettingScript \"\$FileName\";\r\ + \n#:execute script=\"[[:parse \\\"[:parse { :put (\"\$[/system script get \ + [/system script find name=\$FileName] source]\"); } \\\"]]\"; \\\r\ + \n\r\ + \n#:execute script={:put \"[:parse \\\"[:parse [/system/script get [/syste\ + m/script find name=\$gettingScript] source];\\\"]]\";} \\\r\ + \n# file=(\$FilePath . \".rsc\\00\");\r\ + \n:do {\r\ + \n:local fName (\$FilePath . \".rsc\");\r\ + \n#:delay 3s;\r\ + \n#/file add name=\$fName;\r\ + \n:put [:tostr [\$expSrc]];\r\ + \n/file set \$fName contents=[:put [\$expSrc]];\r\ + \n:delay 3s;\r\ + \n\$WaitForFile \$fName;\r\ + \n(\$FilePath . \".rsc\");\r\ + \n#:delay 2s;\r\ + \n\r\ + \n} on-error={\r\ + \n:log error \"\\nERROR \\\$FilePath = \$FilePath\";\r\ + \n}\r\ + \n#\$WaitForFile (\$FilePath . \".rsc\");\r\ + \n#:execute script={ :put [/system/script get [:put \$FileName] source]; }\ + ;\r\ + \n#/system/script export \$Sscript file=\$FilePath;\r\ + \n#/system/script/export [:put [/system/script/get \$expScript source]] fi\ + le=\$FilePath;\r\ + \n#:execute script={ :put [/system/script/get \$expScript source]; } \\\r\ + \n\r\ + \n#:log warning \"\\n\\\$expScript = \$expScript\";\r\ + \n# \\\r\ + \n# file=(\$FilePath . \".rsc\\00\");\r\ + \n#script={:put [/system/script/get \$FileName source];} file=(\$FilePath\ + \_. \".rsc\\00\");\r\ + \n#/system script export \$src file=(\$FilePath . \$FileName);\r\ + \n}\r\ + \n\r\ + \n:log warning \"\\nComplete!\";\r\ + \n\r\ + \n # :execute script={ :put [ /system/script/get Amster-GlobalConfig sou\ + rce ]; } \\\r\ + \n # file=(\$FilePath . \".conf\\00\");\r\ + \n # \$WaitForFile (\$FilePath . \".conf\");\r\ + \n # :onerror Err {\r\ + \n # /tool/fetch upload=yes url=(\$BackupUploadUrl . \"/\" . \$FileName\ + \_. \".conf\") \\\r\ + \n # user=\$BackupUploadUser password=\$BackupUploadPass src-path=(\ + \$FilePath . \".conf\");\r\ + \n # :set ConfigFile [ /file/get (\$FilePath . \".conf\") ];\r\ + \n # :set (\$ConfigFile->\"name\") (\$FileName . \".conf\");\r\ + \n " +add dont-require-permissions=no name=test1 owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="\ + \r\ + \n#:local runningVer [:find [/system/resource/get version] \"(\"];\r\ + \n\r\ + \n:local runningChannel;\r\ + \n:local runningVersion;\r\ + \n:local ParseCustom;\r\ + \n\r\ + \n:set ParseCustom do={\r\ + \n :local Str [ :tostr \$1 ];\r\ + \n :local Start [:find [ \$Str ] [:tostr \$2]];\r\ + \n :local End [ :tostr \$3 ];\r\ + \n :local Num [ :tonum \$4 ];\r\ + \n :local Return;\r\ + \n\r\ + \n :if ([:len \$Num] = 0) do={:set Num \"0\"}\r\ + \n :local rest [:pick \$Str (\$Start+\$Num) [:len \$Str]]; \r\ + \n :if ([:len \$End ] = 0) do={:set Return [:pick \$Str \$Num \$Start]} e\ + lse={\r\ + \n :set End [:find \$rest \$End];\r\ + \n :set Return [:pick \$rest 0 \$End];\r\ + \n }\r\ + \n:return \$Return;\r\ + \n}\r\ + \n \r\ + \n:global runningChannel [\$ParseCustom [/system/resource/get version] \"(\ + \" \")\" 1];\r\ + \n:log warning [\$runningChannel];\r\ + \n:set runningVersion [\$ParseCustom [/system/resource/get version] \" \" \ + \")\" 2];\r\ + \n:log warning [\$runningVersion];\r\ + \n\r\ + \n\r\ + \n\r\ + \n# :if ([:len \$open] = 0) do={\r\ + \n# :log error \"\$SMP Could not extract installed OS channel from vers\ + ion string: `\$runningOsAndChannel`.\";\r\ + \n# :error \"\$SMP error, check logs\";\r\ + \n# }\r\ + \n# :local rest [:pick \$runningOsAndChannel (\$open+1) [:len \$runningOs\ + AndChannel]];\r\ + \n# :local close [:find \$rest \")\"];\r\ + \n# :local channel [:pick \$rest 0 \$close];\r\ + \n# :return \$channel;\r\ + \n#}" +add dont-require-permissions=no name=01-Amster-Init-Script owner=vados \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source="# Amster Initial Script\r\ + \n#\r\ + \n#:global GlobalEnvRemove true; /system/script {run Amster-GlobalEnvRemov\ + e; };\r\ + \n#:delay 1s;\r\ + \n:#global ReloadGlobal true; \r\ + \n\r\ + \n:global AmScriptsUrl \"https://ros.vados.ru/\";\r\ + \n:global defOwner \"vados\";\r\ + \n:global AmFirstRun;\r\ + \n\r\ + \n:set AmFirstRun do={\r\ + \n :global AmScriptsUrl;\r\ + \n :global defOwner;\r\ + \n #:global ParseKeyValueStore;\r\ + \n #:local AmUpdCRLF true;\r\ + \n #:local IfThenElse;\r\ + \n #:set IfThenElse do={:if ([ :tostr \$1 ] = \"true\" || [ :tobool \$1\ + \_] = true) do={:return \$2}; :return \$3}\r\ + \n #/certificate/settings/set builtin-trust-store=fetch;\r\ + \n #/tool/fetch \"\$scriptsUrl/certs/Root-YE.pem\" dst-path=\"root-ye.p\ + em\";\r\ + \n #/certificate/import file-name=\"root-ye.pem\" passphrase=\"\";\r\ + \n # For basic verification we rename the certificate and print it by f\ + ingerprint. Make sure exactly this one certificate (\"Root-YE\") is shown.\ + \r\ + \n #/certificate/set name=\"Root-YE\" [ find where common-name=\"Root Y\ + E\" ];\r\ + \n #/certificate/print proplist=name,fingerprint where fingerprint=\"e1\ + 4ffcad5b0025731006caa43a121a22d8e9700f4fb9cf852f02a708aa5d5666\";\r\ + \n #:log info \"\\nAmScriptsUrl = \$AmScriptsUrl\";\r\ + \n :local Scripts {\r\ + \n \"Amster-GlobalConfig\"\r\ + \n \"Amster-GlobalFunc\";\r\ + \n# \"Amster-SFTP-backup\"\r\ + \n# \"Amster-BkpEmail-and-upd\";\r\ + \n };\r\ + \n# /system/script/set owner=(\$ScriptVal->\"name\") \\\r\ + \n# source=[ \$IfThenElse (\$ScriptUpdatesCRLF = true) \$SourceCRLF \$S\ + ourceNew ] \$Script;\r\ + \n :foreach Script in=\$Scripts do={\r\ + \n :put \"Installing \$Script...\";\r\ + \n /system/script/remove [ find where name=\$Script ];\r\ + \n /system/script/add name=\$Script owner=\$defOwner source=([ /too\ + l/fetch (\"\$AmScriptsUrl\" . \$Script . \".rsc\") output=user as-value ]-\ + >\"data\");\r\ + \n /system/script/set source=[ :tocrlf [ get \$Script source ] ] \$\ + Script;\r\ + \n };\r\ + \n /system/script { run Amster-GlobalConfig; run Amster-GlobalFunc; }\r\ + \n}\r\ + \n:global AmUpdRun;\r\ + \n:set AmUpdRun do={\r\ + \n #:local UpdScript [ :tostr \$1 ];\r\ + \n #:global ParseKeyValueStore;\r\ + \n :global AmScriptsUrl;\r\ + \n :global defOwner;\r\ + \n :local Scripts {\r\ + \n# \"Amster-GlobalConfig\"\r\ + \n# \"Amster-GlobalFunc\"\r\ + \n# \"Amster-GlobalRemove\"\r\ + \n \"Amster-backup-SFTP\"\r\ + \n \"Amster-SFTP-backup\"\r\ + \n \"Amster-BkpEmail-and-upd\"\r\ + \n \"Amster-TG-Bot\"\r\ + \n \"Amster-TG-Notification\";\r\ + \n };\r\ + \n :foreach Script in=\$Scripts do={\r\ + \n :put \"updating \$Script...\";\r\ + \n /system/script/remove [ find where name=\$Script ];\r\ + \n /system/script/add name=\$Script owner=\$defOwner source=([ /too\ + l/fetch (\"\$AmScriptsUrl\" . \$Script . \".rsc\") output=user as-value ]-\ + >\"data\");\r\ + \n /system/script/set source=[ :tocrlf [ get \$Script source ] ] \$\ + Script;\r\ + \n };\r\ + \n# /system/script/add name=\$Script owner=\$AmOwner source=([ /tool/fe\ + tch check-certificate=yes-without-crl (\"\$AmScriptsUrl\" . \$Script . \".\ + rsc\") output=user as-value ]->\"data\"); };\r\ + \n}\r\ + \n\r\ + \n\$AmFirstRun;\r\ + \n#\$AmUpdRun;\r\ + \n\r\ + \n#\$setUpdExist;\r\ + \n#:global setUpdExist;\r\ + \n#:global ParseKeyValueStore;\r\ + \n#:set setUpdExist do={\r\ + \n#:do {\r\ + \n#:global ParseKeyValueStore;\r\ + \n# :foreach Script in=[ /system/script/find where source~\"^#!rsc by Vad\ + os\\r\?\\n#comment=*\" ] do={\r\ + \n# :local ScriptVal [ /system/script/get \$Script ];\r\ + \n# :local ScriptInfo [ \$ParseKeyValueStore (\$ScriptVal->\"#comment\"\ + ) ];\r\ + \n# :local scriptName (\$ScriptVal->\"name\");\r\ + \n#:log warning (\"\\n\\\$scriptName = \". \$scriptName);\r\ + \n#: log warning (\"\\n\\\$ScriptInfo = \". \$ScriptInfo);\r\ + \n# }\r\ + \n#} on-error={}\r\ + \n#}\r\ + \n#\$name;\$owner;\$policy;\$dont-require-permissions;\$last-started;\$run\ + -count;\$source;\$invalid;\$comment;\$.id;\$.nextid;\$.dead;\$.about;\r\ + \n#;(evl / (evl /docommand=;(evl / (evl /foreachcounter=\$Script;do=;(evl \ + / (evl /localname=\$ScriptVal;value=(evl (evl /system/script/getnumber=\$S\ + cript))) (evl /localname=\$ScriptInfo;value=(evl (<%% \$ParseKeyValueStore\ + \_(> \$ParseKeyValueStore);(-> \$ScriptVal comment)))) (evl /localname=\$s\ + criptName) (evl /setname=\$scriptName;value=(-> #\$ScriptVal name)) (evl /\ + log/errormessage=(. \r\ + \n# \$ ScriptInfo = \$ScriptInfo)));in=(evl (evl /system/script/findwhere\ + =\$name;\$owner;\$policy;\$dont-require-permissions;\$last-started;\$run-c\ + ount;\$source;\$invalid;\$comment;\$.id;\$.nextid;\$.dead;\$.about;(~ \$so\ + urce (. ^#!rsc by Vados \r\ + \n# \? \r\ + \n#));5))));on-error=;(evl /)))\r\ + \n" +add dont-require-permissions=no name="Download cAP's Packages" owner=vados \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source="# +------------------------------+\r\ + \n# | |\r\ + \n# | Mikrotik RouterOS Scripts |\r\ + \n# | _Download cAP's Packages_ |\r\ + \n# | |\r\ + \n# +------------------------------+\r\ + \n# https://github.com/sebastian13/mikrotik-routeros-scripts\r\ + \n#\r\ + \n\r\ + \n# Set a directory path providing the required space\r\ + \n# Add a microSD card https://wiki.mikrotik.com/wiki/Manual:System/Disks\ + \r\ + \n# or setup a RAM-Disk via '/disk/add type=tmpfs tmpfs-max-size=20M slot=\ + ram'\r\ + \n:local dir \"/tmpfs/capsman-packages\";\r\ + \n\r\ + \n\r\ + \n# Set Package Path if required, set policy to none if using 'upgrade-cap\ + s' script\r\ + \n/interface/wifi/capsman/set package-path=\$dir upgrade-policy=none;\r\ + \n/caps-man/manager/set package-path=\$dir upgrade-policy=none;\r\ + \n#require-same-version;\r\ + \n\r\ + \n:local installed [/system package get value-name=version [find where nam\ + e=\"routeros\"]]\r\ + \n\r\ + \n# Delete old downloads\r\ + \n# /file/remove [ find type=package name~\"capsman*\" ]\r\ + \n\r\ + \n# Starting from RouterOS version 7.13, drivers for older wireless and 60\ + GHz interfaces are\r\ + \n# part of the separate \"wireless\" package. The existing \"wifiwave2\" \ + package has been divided\r\ + \n# into distinct packages: \"wifi-qcom\" and \"wifi-qcom-ac\".\r\ + \n# RouterOS >=7.12 is required to upgrade to 7.13.\r\ + \n# https://forum.mikrotik.com/viewtopic.php\?t=202423\r\ + \n\r\ + \n:foreach architecture in={ \"arm\";\r\ + \n# \"arm64\";\r\ + \n \"x86_64\";\r\ + \n \"mipsbe\"} do={\r\ + \n :foreach package in={ \"routeros\";\r\ + \n# \"wifi-qcom-ac\";\r\ + \n# \"wifi-qcom\";\r\ + \n \"container\";\r\ + \n \"wireless\"} do={ :do {\r\ + \n :put \"\"\r\ + \n :put \"Download \$package \$installed for \$architecture\"\r\ + \n /tool fetch mode=https \\\r\ + \n url=\"https://download.mikrotik.com/routeros/\$installed/\$packa\ + ge-\$installed-\$architecture.npk\" \\\r\ + \n dst-path=\"\$dir/\$package-\$installed-\$architecture.npk\"\r\ + \n } on-error={ :put \"Error downloading the file. On a 404, note t\ + hat the package may not exist for the specified architecture!\" }\r\ + \n }\r\ + \n}\r\ + \n" +add comment="Script for setup routeros script system" \ + dont-require-permissions=no name=AM-setup owner=AM-setup policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: AM-setup\r\ + \n# Script comment: Script for setup routeros script system\r\ + \n# Copyright (c) 2005-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# requires device-mode, fetch\r\ + \n#\r\ + \n# :local ScriptName [ :jobname ];\r\ + \n# Additional commands:\r\ + \n#\r\ + \n# For scripts:\r\ + \n#:global NewScript \"script_name\"; \r\ + \n#:global GlobalScriptsUrl \"https://ros.vados.ru/\";\r\ + \n#/system/script/add name=\$NewScript owner=\$NewScript source=([/tool/fe\ + tch check-certificate=yes-without-crl (\$GlobalScriptsUrl . \$ScriptAdd . \ + \".rsc\") output=user as-value ]->\"data\");\r\ + \n#:global NewScript (a);\r\ + \n#:global ScriptInstallUpdate; \$ScriptInstallUpdate;\r\ + \n#\r\ + \n# For run from terminal:\r\ + \n#/system/script/add name=AM-setup owner=AM-Setup source=([/tool/fetch ch\ + eck-certificate=yes-without-crl \"https://ros.vados.ru/AM-setup.rsc\" outp\ + ut=user as-value ]->\"data\");\r\ + \n#\r\ + \n\r\ + \n:global ScriptUpdatesCRLF \"LF\";\r\ + \n#:if (\$ScriptUpdatesCRLF != false) do={\r\ + \n# :foreach Script in=[ /system/script/find where source~\"^#!rsc by Vad\ + os\" ] do={\r\ + \n# if (\$ScriptUpdatesCRLF = true) do={\r\ + \n# /system/script/set source=[ :tocrlf [ get \$Script source ] ] \$S\ + cript;\r\ + \n# } else={\r\ + \n# /system/script/set source=[ :tolf [ get \$Script source ] ] \$Scr\ + ipt;\r\ + \n# }\r\ + \n# }\r\ + \n#}\r\ + \n\r\ + \n# /import verbose=no \$ScriptName\r\ + \n:global Identity [/system/identity/get name];\r\ + \n:global SetupScript [ :jobname ];\r\ + \n:global RemoveMe true;\r\ + \n:global RunOnce (a);\r\ + \n:global InitSetup true;\r\ + \n:global tmpfs false;\r\ + \n\r\ + \n{\r\ + \n :local RmMe [:tobool \$1];\r\ + \n\r\ + \n :global GlobalScriptsUrl;\r\ + \n :global SetupScript;\r\ + \n :global RemoveMe;\r\ + \n :global InitSetup;\r\ + \n :global tmpfs;\r\ + \n\r\ + \n :local UrlSuffix \$Identity;\r\ + \n :local UrlDir \"certs/\";\r\ + \n :local CertUrl (\$GlobalScriptsUrl . \$UrlDir . \$UrlSuffix .\"/\"); \ + \r\ + \n :local CertCommonName \"Root YE\";\r\ + \n :local CertFileName \"Root-YE.pem\";\r\ + \n :local CertFingerprint \"e14ffcad5b0025731006caa43a121a22d8e9700f4fb9c\ + f852f02a708aa5d5666\";\r\ + \n\r\ + \n :if (\$SetupScript = false) do={\r\ + \n :global SetupScript \"AM-setup\";\r\ + \n }\r\ + \n :if (\$RemoveMe != false) do={\r\ + \n :set RemoveMe true;\r\ + \n }\r\ + \n\r\ + \n :log warning (\"\\n\\\$GlobalScriptsUrl = \" . \$GlobalScriptsUrl);\r\ + \n \r\ + \n\r\ + \n :if (!(((\$CertSettings->\"builtin-trust-anchors\") = \"trusted\" ||\ + \_\\\r\ + \n (\$CertSettings->\"builtin-trust-store\") ~ \"fetch\" || \\\r\ + \n (\$CertSettings->\"builtin-trust-store\") = \"all\") && \\\r\ + \n [ :len [ /certificate/builtin/find where common-name=\$CertComm\ + onName ] ] > 0)) do={\r\ + \n :put \"Importing certificate...\";\r\ + \n /tool/fetch (\$CertUrl . \$CertFileName) dst-path=\$CertFileName\ + \_as-value;\r\ + \n :delay 1s;\r\ + \n /certificate/import file-name=\$CertFileName passphrase=\"\";\r\ + \n /certificate/set name=\"Root-YE\" [ find where common-name=\"Roo\ + t YE\" ];\r\ + \n :delay 1s;\r\ + \n };\r\ + \n :local CertSettings [ /certificate/settings/get ];\r\ + \n :if ([ :len [ /certificate/find where fingerprint=\$CertFingerprint ] \ + ] != 1) do={\r\ + \n :error \"Something is wrong with your certificates!\";\r\ + \n };\r\ + \n\r\ + \n:local MkTmpfs do={\r\ + \n :local TmpFs [ /disk/find where slot=tmpfs type=tmpfs ];\r\ + \n :if ([ :len \$TmpFs ] = 1) do={\r\ + \n :if ([ /disk/get \$TmpFs disabled ] = true) do={\r\ + \n :log info (\"The tmpfs is disabled, enabling.\");\r\ + \n /disk/enable \$TmpFs;\r\ + \n }\r\ + \n :return true;\r\ + \n }\r\ + \n :log info (\"Try to removing directory 'tmpfs'.\");\r\ + \n :do {\r\ + \n :retry {\r\ + \n /file/remove tmpfs;\r\ + \n :return true;\r\ + \n } delay=100ms max=10;\r\ + \n } on-error={ }\r\ + \n# \$RmDir \"tmpfs\";\r\ + \n :onerror Err {\r\ + \n /disk/add slot=tmpfs type=tmpfs tmpfs-max-size=([ /system/resource/g\ + et total-memory ] / 3);\r\ + \n :do {\r\ + \n :retry {\r\ + \n /file/get tmpfs;\r\ + \n :return true;\r\ + \n } delay=222ms max=10;\r\ + \n } on-error={ }\r\ + \n } do={\r\ + \n :log warning (\"Creating disk of type tmpfs failed: \" . \$Err);\r\ + \n :return false;\r\ + \n }\r\ + \n# :return true;\r\ + \n}\r\ + \n:if (\$tmpfs = true) do={\r\ + \n :put \"Tmpfs variable is true. Checking for tmpfs/scripts directory...\ + \";\r\ + \n :if ([/file find name=\"tmpfs/scripts\"] != \"\") do={\r\ + \n :log info (\$0 . \"Directory tmpfs/scripts exsist.\");\r\ + \n :return \$true;\r\ + \n } else={\r\ + \n :log warning (\$0 . \"Directory tmpfs/scripts does't exsist.\");\r\ + \n :if ([ \$MkTmpfs ] = false) do={\r\ + \n :log error (\$0 . \"Directory tmpfs is not created!\");\r\ + \n :return false;\r\ + \n } else={\r\ + \n :onerror Err {\r\ + \n /file/add type=\"directory\" name=/tmpfs/scripts;\r\ + \n :do {\r\ + \n :retry {\r\ + \n /file/get /tmpfs/scripts;\r\ + \n :return true;\r\ + \n } delay=100ms max=10;\r\ + \n } on-error={ }\r\ + \n } do={\r\ + \n :log warning (\$0 . \"Making directory 'scripts' failed: \" . \$\ + Err);\r\ + \n :return false;\r\ + \n }\r\ + \n# :return true;\r\ + \n# [ /file/get \$FileName ]\r\ + \n }\r\ + \n# :log error (\$0 . \"Directory tmpfs/scripts does not exsist. Remove\ + \_global variable tmpfs!\");\r\ + \n# :global tmpfs (a);\r\ + \n# return false;\r\ + \n }\r\ + \n}\r\ + \n :put \"Renaming AM-GlobalConfig, if exists...\";\r\ + \n :local ConfOldName (\"AM-GlobalConfig-\" . [ /system/clock/get date ] \ + . [ /system/clock/get time ] . \".rsc\");\r\ + \n /system/script/set name=\$ConfOldName [ find where name=\"AM-GlobalCon\ + fig\" ];\r\ + \n :foreach Script in={ \"AM-GlobalConfig\"; \"AM-GlobalFunc\" } do={\r\ + \n :put \"Installing \$Script...\";\r\ + \n /system/script/remove [ find where name=\$Script ];\r\ + \n /system/script/add name=\$Script owner=\$Script source=([ /tool/fetc\ + h check-certificate=yes-without-crl url=(\$GlobalScriptsUrl . \$Script . \ + \".rsc\") output=user as-value ]->\"data\");\r\ + \n \r\ + \n /system/script run \$Script;\r\ + \n :delay 2s;\r\ + \n };\r\ + \n\r\ + \n :local Script \"mod/AM-Certs-By-Ident\";\r\ + \n \r\ + \n :put \"Installing \$Script...\";\r\ + \n /system/script/remove [ find where name=\$Script ];\r\ + \n /system/script/add name=\$Script owner=\$Script source=([ /tool/fetch \ + check-certificate=yes-without-crl url=(\$GlobalScriptsUrl . \$Script . \".\ + rsc\") output=user as-value ]->\"data\");\r\ + \n /system/script run \$Script;\r\ + \n :global tmpAddCerts;\r\ + \n :while ([ \$tmpAddCerts ] = true) do={\r\ + \n :delay 1s;\r\ + \n }\r\ + \n :delay 1s;\r\ + \n :put \"Removing \$Script...\";\r\ + \n /system/script/remove [ find where name=\$Script ];\r\ + \n\r\ + \n :if ([ :len [ /certificate/find where fingerprint=\$CertFingerprint ] \ + ] > 0) do={\r\ + \n :put \"Renaming certificate by its common-name...\";\r\ + \n :global CertificateNameByCN;\r\ + \n \$CertificateNameByCN \$CertFingerprint;\r\ + \n };\r\ + \n\r\ + \n :if (\$RemoveMe = true) do={\r\ + \n :put \"Add Scheduler for remove this script...\";\r\ + \n :local OnEvent (\":delay 30s;\\n\" . \"/system/script/remove [ find \ + where name=\\\"\$SetupScript\\\" ];\\n\" . \\\r\ + \n \":delay 2s;\\n\" . \"/system/scheduler/remove [ find where name=\\\ + \"_RemoveSetup\\\" ];\\n\" . \\\r\ + \n \":delay 2s;\\n\" . \":global RemoveMe (a);\\n\");\r\ + \n :local Comment \"_RemoveSetup tmp scheduler for remove setup script.\ + \";\r\ + \n /system/scheduler/add name=\"_RemoveSetup\" comment=\$Comment start-\ + time=startup interval=30s on-event=\$OnEvent;\r\ + \n :log warning (\$0 . \"\\nI've done everything, and now I'm tired..\ + . I'm leaving.\\nThere will be an error below, this is normal.\");\r\ + \n }\r\ + \n :delay 1;\r\ + \n :put \"Loading configuration and functions...\";\r\ + \n :do { \r\ + \n :global ScriptInstallUpdate; \$ScriptInstallUpdate;\r\ + \n :put \"Normal removing temporary global environment variables.\";\ + \r\ + \n :global SetupScript (a);\r\ + \n :global InitSetup (a);\r\ + \n :global RemoveMe (a);\r\ + \n } on-error={\r\ + \n :local ErrorMsg \"Loading configuration and functions error!\";\r\ + \n :log error (\$0 . \$ErrorMsg . :error);\r\ + \n :error \$ErrorMsg;\r\ + \n }\r\ + \n}\r\ + \n" +add comment="Global Config Script" dont-require-permissions=no name=\ + AM-GlobalConfig owner=AM-GlobalConfig policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: AM-GlobalConfig\r\ + \n# Script comment: Global Config Script\r\ + \n# Copyright (c) 2007-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# Warning: Dont touch this line!\r\ + \n:global Identity [/system/identity/get name];\r\ + \n:global GlobalConfReady false;\r\ + \n#\r\ + \n#\r\ + \n# Global Url For Amster scripts to fetch\r\ + \n# most be defined in AM-Init-Script or AM-setup scripts\r\ + \n:global GlobalScriptsUrl \"https://ros.vados.ru/\";\r\ + \n:global ScriptsUrlSuffix (a);\r\ + \n:global CheckSumsVerify true;\r\ + \n\r\ + \n:global ScriptUpdatesCRLF true;\r\ + \n:global CommentsInScripts true;\r\ + \n\r\ + \n# Debug output:\r\ + \n:global PrintDebug true; #false;\r\ + \n\r\ + \n# Debug output for specific script:\r\ + \n#:set (\$PrintDebugOverride->\"AM-GlobalFunc\") true;\r\ + \n\r\ + \n# Debug logs\r\ + \n# Other actions (disk, email, remote or support) can be used as well.\r\ + \n# I do not recommend using echo - use debug output instead (https://rsc.\ + eworm.de/main/DEBUG.html#debug-output).\r\ + \n#/system/logging/add topics=script,debug action=memory;\r\ + \n\r\ + \n:global MikrotikUpgradeUrl \"https://upgrade.mikrotik.com/routeros\";\r\ + \n:global Domain \"WORKGROUP\";\r\ + \n\r\ + \n:global EmailGeneralTo \"vados@vados.ru\";\r\ + \n#:global EmailGeneralCc \"\";\r\ + \n\r\ + \n# \$ScriptInstallUpdate mod/notification-telegram\r\ + \n:global TelegramTokenId \"8390868324:AAG3_d1hfVR2M6pGAgDK6ZvhOe4J97nyopw\ + \";\r\ + \n:global TelegramChatId \"-1003631309063\"; #\"454403445\";\r\ + \n# Use this to send notifications to a specific topic in group.\r\ + \n#:global TelegramThreadId \"30\";\r\ + \n# Using telegram-chat you have to define trusted chat ids (not group ids\ + !)\r\ + \n# or user names. Groups allow to chat with devices simultaneously.\r\ + \n:global TelegramChatIdsTrusted {\r\ + \n \"454403445\"\r\ + \n \"-1003631309063\"\r\ + \n \"-5124482807\"\r\ + \n \"1087968824\";\r\ + \n};\r\ + \n# \"-5294757340\";\r\ + \n\r\ + \n:global TelegramChatGroups \"(all)\";\r\ + \n#:global TelegramChatGroups \"(all|home|office)\";\r\ + \n\r\ + \n:global NotificationFunctions {\r\ + \n \"email\"\r\ + \n \"telegram\";\r\ + \n};\r\ + \n# Toggle this to disable symbols in notifications.\r\ + \n:global NotificationsWithSymbols true;\r\ + \n:global TerminalColorOutput true;\r\ + \n\r\ + \n:global DetectIpAddrDefault \"https://ipv4.mikrotik.ovh/\";\r\ + \n:global DetectIpAddrFallback \"https://api.ipify.org/\";\r\ + \n\r\ + \n## Update channel. Possible values: stable, long-term, testing, developm\ + ent\r\ + \n:global updateChannel \"stable\";\r\ + \n\r\ + \n:global CertRenewUrl (\$GlobalScriptsUrl . \"certs/\" . \$Identity . \"/\ + \");\r\ + \n:global CertRenewTime 2w;\r\ + \n:global CertRenewPass (a); #{\r\ + \n# \"\";\r\ + \n# \"v3ry-s3cr3t\";\r\ + \n# \"4n0th3r-s3cr3t\";\r\ + \n#};\r\ + \n:global CertWarnTime 1w;\r\ + \n:global CertIssuedExportPass (a); # {\r\ + \n# \"\";\r\ + \n# \"cert1-cn\"=\"v3ry-s3cr3t\";\r\ + \n# \"cert2-cn\"=\"4n0th3r-s3cr3t\";\r\ + \n#};\r\ + \n\r\ + \n# Script BackupAndUpdate mode, possible values: backup,\r\ + \n# updateOnce - Update once as \"osnotify\" and back \"osnotify\" mode (i\ + n terminal run :set OSUpdateMode updateOnce; \$OSUpdateMode;). \r\ + \n# osupdate - Update if available and creates backups before/after update\ + \_(ignores `forceBackup`)\r\ + \n# Set `forceBackup` to true to always create backups, even\ + \_without updates\r\ + \n# osnotify - Set `forceBackup` to always create backups on every run\r\ + \n:global DevInfoIncludeIP true;\r\ + \n:global BackupAndUpdateMode \"osnotify\"\r\ + \n\r\ + \n:global forceBackup true;\r\ + \n:global BackupPassword \"Peskar55\";\r\ + \n\r\ + \n# SFTP Backup Variables\r\ + \n:global BackupRandomDelay 0;\r\ + \n:global BackupUploadUrl \"sftp://amster.vados.ru/upload/\";\r\ + \n:global BackupUploadUser \$Identity;\r\ + \n:global BackupUploadPass \"root.mnuc.backuper\";\r\ + \n\r\ + \n:global BackupSendBinary true;\r\ + \n:global BackupSendExport true;\r\ + \n:global BackupSendGlobalConfig true;\r\ + \n:global BackupSendScripts true;\r\ + \n\r\ + \n# Remove local file after uploading if no errors\r\ + \n:global BackupRmLocal true;\r\ + \n\r\ + \n# Local Backups Root Directory \r\ + \n:global BackupLocalRoot \"tmpfs\";\r\ + \n\r\ + \n# Encrypt Backup\r\ + \n:global BackupEncrypt true;\r\ + \n\r\ + \n# Sensitive information in Backups\r\ + \n:global BackupSens true;\r\ + \n\r\ + \n# User Export\r\ + \n:global BackupUser true;\r\ + \n\r\ + \n# License Export (not for CHR, will silently skip)\r\ + \n:global BackupLicense false;\r\ + \n\r\ + \n# SSH Keys\r\ + \n:global BackupSshKeys true;\r\ + \n\r\ + \n# Certificate Export\r\ + \n:global BackupCerts true;\r\ + \n\r\ + \n# Certificate Password\r\ + \n:global BackupCertsPasswd \"\";\r\ + \n\r\ + \n# User-Manager Export\r\ + \n:global BackupUserMan false;\r\ + \n\r\ + \n# The Dude Export\r\ + \n:global BackupDude false;\r\ + \n\r\ + \n# User Files to export, comma-separated string or array of strings\r\ + \n# User Files are not removed on backup\r\ + \n# Any directory paths will be removed (/ -> _) on remote file\r\ + \n# Nonpresent files are silently skipped\r\ + \n:global BackupUserList \"autosupout.rif,autosupout.old.rif\";\r\ + \n\r\ + \n# load functions and any custom scripts\r\ + \n# [ /system/script/find where name=\"global-config-ov\ + erlay\" ], \\\r\ + \n# [ /system/script/find where name~\"^global-config-o\ + verlay\\\\.d/.\" ]\r\ + \n#\r\ + \n#:foreach Script in=([ /system/script/find where name=\"Amster-TGglobalV\ + ars\" ], [ /system/script/find where name=\"Amster-TGglobalFunc\" ]) do={\ + \r\ + \n# :onerror Err {\r\ + \n# /system/script/run \$Script;\r\ + \n# } do={\r\ + \n# :log error (\"Loading configuration from configs and functions\"\ + \_. [ /system/script/get \$Script name ] . \" failed: \" . \$Err);\r\ + \n# }\r\ + \n#}\r\ + \n#:log info \"\\ndevMode = \$devMode\";\r\ + \n#:log info \"\\ndevRunning = \$devRunning\";\r\ + \n:foreach Script in=[ /system/script/find where source~\"^#!rsc by Vados \ + GlobalRUN\\r\?\\n\" ] do={\r\ + \n :onerror Err {\r\ + \n /system/script/run \$Script;\r\ + \n } do={\r\ + \n :log error (\"Run script \" . [ /system/script/get \$Script name \ + ] . \" width marker \\\" GlobalRUN\\\" failed: \" . \$Err);\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n# signal we are ready\r\ + \n:set GlobalConfReady true;\r\ + \n:while (\$GlobalConfReady != true) do={:delay 500ms};\r\ + \n" +add comment="Global functions" dont-require-permissions=no name=AM-GlobalFunc \ + owner=AM-GlobalFunc policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: AM-GglobalFunc\r\ + \n# Script comment: Global functions\r\ + \n# Copyright (c) 2005-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# requires device-mode, fetch, scheduler\r\ + \n:local ExitOK false;\r\ + \n:local ScriptName [ :jobname ];\r\ + \n#:global checkRunJob \$SriptName;\r\ + \n#:global fixCRLF;\r\ + \n#:if (\$fixCRLF != false) do={\r\ + \n#:foreach fixScript in=([ /system/script/find where name~\"*\" ]) do={/s\ + ystem/script/set source=[ :tocrlf [ get \$fixScript source ] ] \$fixScript\ + }\r\ + \n#:if (\$fixCRLF != \"LF\") do={ /system/script/set source=[ :tocrlf [ g\ + et \$fixScript source ] ] \$fixScript};};\r\ + \n#}\r\ + \n:global GlobalFuncReady false;\r\ + \n\r\ + \n# Git commit id & info, expected configuration version\r\ + \n:global CommitId \"main/729/f42d43ae\";\r\ + \n:global CommitTAG \"unknown\";\r\ + \n:global CommitInfo \"Autocommit in repo: ros.vados.ru, generated by owne\ + r: root, in 00:05:03 01.07.2026.\";\r\ + \n\r\ + \n# The variables are received right here.\r\ + \n:global GlobalConfVersion 140;\r\ + \n\r\ + \n:global SumsDirAndFileName \"tmp/checksums.json\";\r\ + \n\r\ + \n:global Identity [/system/identity/get name];\r\ + \n:global IdentityShort [:pick \$Identity 0 18];\r\ + \n\r\ + \n:global devBoardName [/system/resource/get board-name];\r\ + \n:global devMode [/system/device-mode/get mode];\r\ + \n:global devCurArch [/system/resource/get architecture-name];\r\ + \n:global devCurVersion [/system/resource/get version];\r\ + \n\r\ + \n# Variables defined in AM-GlobalConfig.rsc\r\ + \n:global TerminalColorOutput;\r\ + \n:global CommentsInScripts;\r\ + \n:global DetectIpAddrDefault;\r\ + \n:global DetectIpAddrFallback;\r\ + \n:global updateChannel;\r\ + \n:global Domain;\r\ + \n:global EmailGeneralTo;\r\ + \n:global BackupAndUpdateMode;\r\ + \n\r\ + \n# Single functions, without nested functions.\r\ + \n:global MiniDateTimeStamp;\r\ + \n:global MicroDateTimeStamp;\r\ + \n\r\ + \n# Include globals \$PrintDebug and \$PrintDebugOverride\r\ + \n:global LogPrint;\r\ + \n\r\ + \n# DeviceInfo and Updates\r\ + \n:global IsCheckUpdates;\r\ + \n:global WaitCheckUpdates;\r\ + \n:global GetPubIp;\r\ + \n\r\ + \n:global GetArrDeviceInfo;\r\ + \n:global WaitArrDeviceInfo;\r\ + \n\r\ + \n:global devModel;\r\ + \n:global devRunningMode;\r\ + \n\r\ + \n:global devRbRevision;\r\ + \n:global devRbSerialNumber;\r\ + \n:global devRbVerAvail;\r\ + \n\r\ + \n:global runningChannelVersion;\r\ + \n:global runningChannel;\r\ + \n:global runningVersion;\r\ + \n\r\ + \n:global devBuildTime;\r\ + \n:global ROsVerAvail;\r\ + \n:global PkgUpdStatus;\r\ + \n:global currentUpdateChannel;\r\ + \n\r\ + \n:global ScriptInstallUpdate;\r\ + \n\r\ + \n:global DeviceInfo;\r\ + \n\r\ + \n:global OldMode;\r\ + \n\r\ + \n:global ParseKeyValueStore;\r\ + \n:global RandomDelay;\r\ + \n:global GetRandomNumber;\r\ + \n:global EscapeForRegEx;\r\ + \n:global CharacterMultiply;\r\ + \n:global CharacterReplace;\r\ + \n:global CleanFilePath;\r\ + \n:global CleanName;\r\ + \n\r\ + \n:global CommitBrief;\r\ + \n\r\ + \n:global EitherOr;\r\ + \n:global FormatLine;\r\ + \n:global GetRandom20CharAlNum;\r\ + \n:global GetRandom20CharHex;\r\ + \n:global GetRandomNumber;\r\ + \n:global Grep;\r\ + \n:global IfThenElse;\r\ + \n:global MAX;\r\ + \n:global MIN;\r\ + \n:global HumanReadableNum;\r\ + \n\r\ + \n:global IsFullyConnected;\r\ + \n:global IsDNSResolving;\r\ + \n:global IsTimeSync;\r\ + \n:global IsTimeSyncCached;\r\ + \n:global IsTimeSyncResetNtp;\r\ + \n\r\ + \n:global MkDir;\r\ + \n:global RmDir;\r\ + \n:global RmFile;\r\ + \n:global FileGet;\r\ + \n:global Unix2Dos;\r\ + \n:global UrlEncode;\r\ + \n:global WaitForFile;\r\ + \n:global ScriptLock;\r\ + \n:global SendTelegram;\r\ + \n:global NotificationFunctions;\r\ + \n:global SendNotification;\r\ + \n:global SendNotification2;\r\ + \n:global SymbolForNotification;\r\ + \n:global SymbolByUnicodeName;\r\ + \n:global ValidateSyntax;\r\ + \n:global CheckSumsVerify;\r\ + \n:global GlobalConfChanges;\r\ + \n:global GlobalConfMigration;\r\ + \n:global VersionToNum;\r\ + \n:global RequiredRouterOS;\r\ + \n:global ExitError;\r\ + \n:global ExitOnError;\r\ + \n\r\ + \n:global FetchHugeFiles;\r\ + \n:global FetchUserAgentStr;\r\ + \n:global FileExists;\r\ + \n:global CertificateAvailable;\r\ + \n:global CertificateDownload;\r\ + \n:global CertificateNameByCN;\r\ + \n:global CharacterMultiply;\r\ + \n\r\ + \n:global InspectVar;\r\ + \n:global InspectVarRet;\r\ + \n:global ParseCustom;\r\ + \n\r\ + \n:set IsCheckUpdates do={\r\ + \n :local isInfo [:tobool \$1];\r\ + \n :global updateChannel;\r\ + \n :global PkgUpdStatus;\r\ + \n :global currentUpdateChannel;\r\ + \n :global ROsVerAvail;\r\ + \n :global arrDeviceInfo;\r\ + \n :log info (\$0 . \" \\\$isInfo = \" . \$isInfo); \r\ + \n :local CurrentChannel [ /system/package/update/get channel ];\r\ + \n :if ([ :len \$updateChannel ] > 0 && \$CurrentChannel != \$updateChann\ + el) do={\r\ + \n :retry command={/system/package/update set channel=\$updateChannel} \ + \_delay=300ms max=5 on-error={};\r\ + \n :log info (\$0 . \" Set updates channel to: \$updateChannel\");\r\ + \n }\r\ + \n :local Update [ /system/package/update/get ];\r\ + \n :local Channel [ :tostr (\$Update->\"channel\") ];\r\ + \n :set currentUpdateChannel \$Channel;\r\ + \n :log info (\$0 . \" Update Channel: \" . \$Channel . \".\");\r\ + \n :local StatusPkg \"undefined\";\r\ + \n :log info (\$0 . \" Checking for updates...\");\r\ + \n :retry command={:set \$StatusPkg ([/system/package/update/check-for-up\ + dates without-paging as-value]->\"status\")} \\\r\ + \n delay=300ms max=5 on-error={};\r\ + \n :log info (\$0 . \" \\\$StatusPkg = \" . \$StatusPkg);\r\ + \n :if (\$StatusPkg = \"New version is available\") do={ :set PkgUpdStatu\ + s true; } else={ :set PkgUpdStatus false; }\r\ + \n :set ROsVerAvail [ :tostr [ /system/package/update/get latest-version \ + ] ];\r\ + \n :log info (\$0 . \" \\\$PkgUpdStatus = \" . \$PkgUpdStatus);\r\ + \n :if (\$isInfo != false || \$PkgUpdStatus = true) do={\r\ + \n :log info (\$0 . \" \\\$PkgUpdStatus = \" . \$PkgUpdStatus);\r\ + \n :if ([ :typeof \$arrDeviceInfo ] != \"array\") do={\r\ + \n :set arrDeviceInfo [:toarray \$arrDeviceInfo];\r\ + \n }\r\ + \n :retry command={:set (\$arrDeviceInfo->\"devUpdates\") [ /system/pac\ + kage/update/get ]} delay=500ms max=5 on-error={:log error (\"Error\")};\r\ + \n :return true;\r\ + \n } else={\r\ + \n :return \$PkgUpdStatus;\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set WaitCheckUpdates do={\r\ + \n :global IsCheckUpdates;\r\ + \n :global PkgUpdStatus;\r\ + \n :global currentUpdateChannel;\r\ + \n :global ROsVerAvail;\r\ + \n :global arrDeviceInfo;\r\ + \n :local Status \"\";\r\ + \n :local Update;\r\ + \n :local WaitCount 0;\r\ + \n :if ([ \$IsCheckUpdates true ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n :set Status [ :tostr [ /system/package/update/get status ] ];\r\ + \n :while ((\$Status = \"\" || \$Status = \"finding out latest version...\ + \") && \$WaitCount < 60) do={\r\ + \n :delay 1s;\r\ + \n :set WaitCount (\$WaitCount + 1);\r\ + \n :set Status [ :tostr [ /system/package/update/get status ] ];\r\ + \n }\r\ + \n :set Update [ /system/package/update/get ];\r\ + \n :set currentUpdateChannel [ :tostr (\$Update->\"channel\") ];\r\ + \n :set ROsVerAvail [ :tostr (\$Update->\"latest-version\") ];\r\ + \n :if ((\$Update->\"status\") = \"New version is available\") do={\r\ + \n :set PkgUpdStatus true;\r\ + \n } else={\r\ + \n :set PkgUpdStatus false;\r\ + \n }\r\ + \n :if ([ :typeof \$arrDeviceInfo ] != \"array\") do={\r\ + \n :set arrDeviceInfo [:toarray \$arrDeviceInfo];\r\ + \n }\r\ + \n :set (\$arrDeviceInfo->\"devUpdates\") \$Update;\r\ + \n :if (\$Status = \"\" || \$Status = \"finding out latest version...\") \ + do={\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set WaitArrDeviceInfo do={\r\ + \n :global GetArrDeviceInfo;\r\ + \n :while ([ \$GetArrDeviceInfo ] = false) do={\r\ + \n :delay 1s;\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set GetArrDeviceInfo do={\r\ + \n :global LogPrint;\r\ + \n :global arrDeviceInfo;\r\ + \n :global devBoardName;\r\ + \n :global devModel;\r\ + \n :global devRunning;\r\ + \n :global devCurArch;\r\ + \n :global devRbRevision;\r\ + \n :global devRbSerialNumber;\r\ + \n :global devBuildTime;\r\ + \n :global runningChannelVersion;\r\ + \n :global runningChannel;\r\ + \n :global runningVersion;\r\ + \n :global currentUpdateChannel;\r\ + \n :global ROsVerAvail;\r\ + \n\r\ + \n :local DeviceInfo;\r\ + \n :local Resource [/system/resource/get];\r\ + \n :local Update [ /system/package/update/get ];\r\ + \n :local VersionString [ :tostr (\$Resource->\"version\") ];\r\ + \n :local ChannelStart [ :find \$VersionString \" (\" ];\r\ + \n :local RouterBoard;\r\ + \n :local IsRouterBoard false;\r\ + \n :local RunningMode \"\";\r\ + \n\r\ + \n :set devCurArch [ :tostr (\$Resource->\"architecture-name\") ];\r\ + \n :set devBuildTime [ :tostr (\$Resource->\"build-time\") ];\r\ + \n :set runningChannelVersion \$VersionString;\r\ + \n :set runningVersion \$VersionString;\r\ + \n :set runningChannel \"\";\r\ + \n\r\ + \n :if ([ :typeof \$ChannelStart ] = \"num\" && \$ChannelStart >= 0) do=\ + {\r\ + \n :set runningVersion [ :pick \$VersionString 0 \$ChannelStart ];\r\ + \n :local ChannelRest [ :pick \$VersionString (\$ChannelStart + 2) [ :\ + len \$VersionString ] ];\r\ + \n :local ChannelEnd [ :find \$ChannelRest \")\" ];\r\ + \n :if ([ :typeof \$ChannelEnd ] = \"num\" && \$ChannelEnd >= 0) do={\ + \r\ + \n :set runningChannel [ :pick \$ChannelRest 0 \$ChannelEnd ];\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if ([:pick \$devBoardName 0 3] != \"CHR\" or [:pick \$devBoardName 0\ + \_3] != \"x86\") do={\r\ + \n :do {\r\ + \n :set RouterBoard [[ :parse \"/system/routerboard/get\" ]];\r\ + \n# :set RouterBoard [ /system/routerboard/get ];\r\ + \n :if ((\$RouterBoard->\"routerboard\") = true) do={\r\ + \n :set IsRouterBoard true;\r\ + \n }\r\ + \n } on-error={}\r\ + \n }\r\ + \n\r\ + \n :do {\r\ + \n :set RunningMode [ /system/device-mode/get mode ];\r\ + \n } on-error={\r\ + \n :do {\r\ + \n :set RunningMode [ /system/device-mode/get running ];\r\ + \n } on-error={\r\ + \n :set RunningMode \"\";\r\ + \n }\r\ + \n }\r\ + \n :set devRunning \$RunningMode;\r\ + \n\r\ + \n :if (\$IsRouterBoard = true) do={\r\ + \n :set devModel [ :tostr (\$RouterBoard->\"model\") ];\r\ + \n :set devRbRevision [ :tostr (\$RouterBoard->\"revision\") ];\r\ + \n :set devRbSerialNumber [ :tostr (\$RouterBoard->\"serial-number\") \ + ];\r\ + \n } else={\r\ + \n :set devModel \$devBoardName;\r\ + \n :set devRbRevision \"\";\r\ + \n :set devRbSerialNumber \"\";\r\ + \n }\r\ + \n\r\ + \n :set currentUpdateChannel [ :tostr (\$Update->\"channel\") ];\r\ + \n :set ROsVerAvail [ :tostr (\$Update->\"latest-version\") ];\r\ + \n\r\ + \n :set (\$DeviceInfo->\"board-name\") \$devBoardName;\r\ + \n :set (\$DeviceInfo->\"architecture-name\") \$devCurArch;\r\ + \n :set (\$DeviceInfo->\"model\") \$devModel;\r\ + \n :set (\$DeviceInfo->\"revision\") \$devRbRevision;\r\ + \n :set (\$DeviceInfo->\"serial-number\") \$devRbSerialNumber;\r\ + \n :set (\$DeviceInfo->\"running-mode\") \$devRunning;\r\ + \n :set (\$DeviceInfo->\"build-time\") \$devBuildTime;\r\ + \n :set (\$DeviceInfo->\"running-channel-version\") \$runningChannelVers\ + ion;\r\ + \n :set (\$DeviceInfo->\"running-version\") \$runningVersion;\r\ + \n :set (\$DeviceInfo->\"running-channel\") \$runningChannel;\r\ + \n :set (\$DeviceInfo->\"routerboard\") \$IsRouterBoard;\r\ + \n :set (\$DeviceInfo->\"current-firmware\") [ :tostr (\$RouterBoard->\"\ + current-firmware\") ];\r\ + \n :set (\$DeviceInfo->\"upgrade-firmware\") [ :tostr (\$RouterBoard->\"\ + upgrade-firmware\") ];\r\ + \n :set (\$DeviceInfo->\"package-channel\") \$currentUpdateChannel;\r\ + \n :set (\$DeviceInfo->\"installed-version\") [ :tostr (\$Update->\"inst\ + alled-version\") ];\r\ + \n :set (\$DeviceInfo->\"latest-version\") \$ROsVerAvail;\r\ + \n :set (\$DeviceInfo->\"update-status\") [ :tostr (\$Update->\"status\"\ + ) ];\r\ + \n\r\ + \n :set arrDeviceInfo \$DeviceInfo;\r\ + \n :return \$DeviceInfo;\r\ + \n}\r\ + \n\r\ + \n:set DeviceInfo do={\r\ + \n :global GetArrDeviceInfo;\r\ + \n :global GlobalConfVersion;\r\ + \n :global Identity;\r\ + \n :global IfThenElse;\r\ + \n :global FormatLine;\r\ + \n :global CommitBrief;\r\ + \n\r\ + \n :local License [/system/license/get];\r\ + \n :local Snmp [ /snmp/get ];\r\ + \n :local Info [ \$GetArrDeviceInfo ];\r\ + \n\r\ + \n :if ([ :typeof \$Info ] != \"array\") do={\r\ + \n :return false;\r\ + \n }\r\ + \n\r\ + \n :return ( \\\r\ + \n [ \$FormatLine \"Hostname\" \$Identity ] . \"\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len (\$Snmp->\"location\") ] > 0) \\\r\ + \n ([ \$FormatLine \"Location\" (\$Snmp->\"location\") ] . \"\\n\") ]\ + \_. \\\r\ + \n [ \$IfThenElse ([ :len (\$Snmp->\"contact\") ] > 0) \\\r\ + \n ([ \$FormatLine \"Contact\" (\$Snmp->\"contact\") ] . \"\\n\") ] .\ + \_\\\r\ + \n \"Hardware:\\n\" . \\\r\ + \n [ \$FormatLine \" Board\" (\$Info->\"board-name\") ] . \"\\n\" . \ + \\\r\ + \n [ \$FormatLine \" Arch\" (\$Info->\"architecture-name\") ] . \"\\\ + n\" . \\\r\ + \n [ \$FormatLine \" Model\" (\$Info->\"model\") ] . \\\r\ + \n [ \$IfThenElse ([ :len (\$Info->\"revision\") ] > 0) \\\r\ + \n (\" \" . (\$Info->\"revision\")) ] . \"\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len (\$Info->\"serial-number\") ] > 0) \\\r\ + \n ([ \$FormatLine \" Serial\" (\$Info->\"serial-number\") ] . \"\ + \\n\") ] . \\\r\ + \n [ \$IfThenElse ([ :len (\$License->\"nlevel\") ] > 0) \\\r\ + \n ([ \$FormatLine \" License\" (\"level \" . (\$License->\"nlevel\ + \")) ] . \"\\n\") ] . \\\r\ + \n \"RouterOS:\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len (\$License->\"level\") ] > 0) \\\r\ + \n ([ \$FormatLine \" License\" (\"level \" . (\$License->\"level\ + \")) ] . \"\\n\") ] . \\\r\ + \n [ \$FormatLine \" Channel\" (\$Info->\"package-channel\") ] . \"\ + \\n\" . \\\r\ + \n [ \$FormatLine \" Installed\" (\$Info->\"installed-version\") ] .\ + \_\"\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len (\$Info->\"running-channel\") ] > 0) \\\r\ + \n ([ \$FormatLine \" Running\" ((\$Info->\"running-version\") . \ + \" (\" . (\$Info->\"running-channel\") . \")\") ] . \"\\n\") \\\r\ + \n ([ \$FormatLine \" Running\" (\$Info->\"running-version\") ] . \ + \"\\n\") ] . \\\r\ + \n [ \$IfThenElse ([ :len (\$Info->\"latest-version\") ] > 0 && \\\r\ + \n (\$Info->\"installed-version\") != (\$Info->\"latest-version\"))\ + \_\\\r\ + \n ([ \$FormatLine \" Available\" (\$Info->\"latest-version\") ] .\ + \_\"\\n\") ] . \\\r\ + \n [ \$IfThenElse ((\$Info->\"routerboard\") = true && \\\r\ + \n (\$Info->\"current-firmware\") != (\$Info->\"upgrade-firmware\")\ + ) \\\r\ + \n ([ \$FormatLine \" Firmware\" ((\$Info->\"current-firmware\") .\ + \_\" -> \" . (\$Info->\"upgrade-firmware\")) ] . \"\\n\") ] . \\\r\ + \n \"RouterOS-Scripts:\\n\" . \\\r\ + \n [ \$FormatLine \" Commit\" [ \$CommitBrief ] ] . \"\\n\" . \\\r\ + \n [ \$FormatLine \" Version\" \$GlobalConfVersion ]);\r\ + \n}\r\ + \n\r\ + \n:if ([ :typeof \$NotificationFunctions ] != \"array\") do={:set Notifica\ + tionFunctions ({})}\r\ + \n\r\ + \n:set GetRandom20CharAlNum do={\r\ + \n :global EitherOr;\r\ + \n :return [ :rndstr length=[ \$EitherOr [ :tonum \$1 ] 20 ] from=\"ABCDE\ + FGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\" ];\r\ + \n}\r\ + \n\r\ + \n:set GetRandom20CharHex do={:global EitherOr; :return [ :rndstr length=[\ + \_\$EitherOr [ :tonum \$1 ] 20 ] from=\"0123456789abcdef\" ]; }\r\ + \n:set GetRandomNumber do={:global EitherOr; :return [ :rndnum from=0 to=[\ + \_\$EitherOr [ :tonum \$1 ] 4294967295 ] ]; }\r\ + \n\r\ + \n:global checkRunJob;\r\ + \n:set checkRunJob do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :global ScriptLock; \r\ + \n :if ([ \$ScriptLock \$Script ] = false) do={:return false}\r\ + \n :foreach Job in=[ /system/script/job/find where script=\$Script ] do={\ + :set Job [ /system/script/job/get \$Job ];\r\ + \n :while ([ :typeof (\$Job->\"parent\") ] = \"id\") do={:set Job [ /sy\ + stem/script/job/get [ find where .id=(\$Job->\"parent\") ] ]}\r\ + \n :if ((\$Job->\"type\") = \"login\") do={:return true};}; :return fa\ + lse; \r\ + \n}\r\ + \n\r\ + \n:set InspectVar do={\r\ + \n :global InspectVarRet;\r\ + \n :put [ :tocrlf [ \$InspectVarRet \$1 ] ];\r\ + \n}\r\ + \n\r\ + \n:set CommitBrief do={\r\ + \n :global CommitId;\r\ + \n :global CommitInfo;\r\ + \n :if (\$CommitId = \"unknown\") do={\r\ + \n :return \"unknown\";\r\ + \n }\r\ + \n :return (\$CommitInfo . \"/\" . [ :pick \$CommitId 0 8 ]);\r\ + \n}\r\ + \n\r\ + \n:set InspectVarRet do={\r\ + \n :local Input \$1;\r\ + \n :local Level (0 + [ :tonum \$2 ]);\r\ + \n :global CharacterReplace;\r\ + \n :global IfThenElse;\r\ + \n :global InspectVarRet;\r\ + \n :local IndentReturn do={\r\ + \n :local Prefix [ :tostr \$1 ];\r\ + \n :local Value [ :tostr \$2 ];\r\ + \n :local Level [ :tonum \$3 ];\r\ + \n :global CharacterMultiply;\r\ + \n :return ([ \$CharacterMultiply \" \" (\$Level * 2) ] . \"-\" . \$Pre\ + fix . \"-> \" . \$Value);\r\ + \n }\r\ + \n :local TypeOf [ :typeof \$Input ];\r\ + \n :local Len [ :len \$Input ];\r\ + \n :local Return [ \$IndentReturn \"type\" \$TypeOf \$Level ];\r\ + \n :if (\$TypeOf = \"array\") do={\r\ + \n :foreach Key,Value in=\$Input do={\r\ + \n :set \$Return (\$Return . \"\\n\" . \\\r\ + \n [ \$IndentReturn \"key\" \$Key (\$Level + 1) ] . \"\\n\" . \\\r\ + \n [ \$InspectVarRet \$Value (\$Level + 2) ]);\r\ + \n }\r\ + \n } else={\r\ + \n :if (\$TypeOf = \"str\") do={\r\ + \n :set \$Return (\$Return . \"\\n\" . \\\r\ + \n [ \$IndentReturn \"len\" \$Len \$Level ]);\r\ + \n :if ([ :typeof [ :find \$Input (\"\\r\") ] ] = \"num\") do={\r\ + \n :set Input [ \$CharacterReplace \$Input (\"\\r\") \"\" ];\r\ + \n }\r\ + \n :if ([ :typeof [ :find \$Input (\"\\n\") ] ] = \"num\") do={\r\ + \n :set Input [ \$CharacterReplace \$Input (\"\\n\") \" \" ];\r\ + \n }\r\ + \n }\r\ + \n :if (\$TypeOf != \"nothing\") do={\r\ + \n :set \$Return (\$Return . \"\\n\" . \\\r\ + \n [ \$IndentReturn \"value\" [ \$IfThenElse ([ :len \$Input ] > 80)\ + \_\\\r\ + \n ([ :pick \$Input 0 77 ] . \"...\") \$Input ] \$Level ]);\r\ + \n }\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set GetPubIp do={\r\ + \n :global DetectIpAddrDefault;\r\ + \n :global DetectIpAddrFallback;\r\ + \n\r\ + \n :local PubIpAddr false;\r\ + \n :local Url \$DetectIpAddrDefault;\r\ + \n :local JobName [ :jobname ];\r\ + \n\r\ + \n :do {\r\ + \n :log debug (\$0 . \" Trying to detect public IP using default serv\ + ice: \" . \$Url . \"...\");\r\ + \n :retry command={:set PubIpAddr ([ /tool/fetch http-method=\"get\"\ + \_check-certificate=yes-without-crl \$Url output=user as-value ]->\"data\"\ + )} \\\r\ + \n delay=50ms max=5 on-error={:log error (\$JobName . \" Failed to \ + detect public IP using default service: \" . \$Url . \".\")}\r\ + \n } on-error={\r\ + \n :log error (\$0 . \" Failed to detect public IP using default serv\ + ice: \" . \$Url . \".\");\r\ + \n :set Url \$DetectIpAddrFallBack;\r\ + \n :log debug (\$0 . \" Trying to detect public IP using fallback ser\ + vice: \" . \$Url . \"...\");\r\ + \n :retry command={:set PubIpAddr ([ /tool/fetch http-method=\"get\"\ + \_check-certificate=yes-without-crl \$Url output=user as-value ]->\"data\"\ + )} \\\r\ + \n delay=200ms max=5 on-error={:log error (\$JobName . \" Failed to\ + \_detect public IP using fallback service: \" . \$Url . \".\")}\r\ + \n };\r\ + \n :local IpLen ([ :len \$PubIpAddr ] -1);\r\ + \n :if (\$PubIpAddr = false) do={:set PubIpAddr \"not-detected\"} else=\ + {:set PubIpAddr [:pick \$PubIpAddr 0 \$IpLen]}\r\ + \n :return \$PubIpAddr;\r\ + \n}\r\ + \n\r\ + \n:set FormatLine do={\r\ + \n :local Key [ :tostr \$1 ];\r\ + \n :local Value [ :tostr \$2 ];\r\ + \n :local Indent [ :tonum \$3 ];\r\ + \n :local Spaces;\r\ + \n :local Return \"\";\r\ + \n :global CharacterMultiply;\r\ + \n :global EitherOr;\r\ + \n :set Indent [ \$EitherOr \$Indent 16 ];\r\ + \n :local Spaces [ \$CharacterMultiply \" \" \$Indent ];\r\ + \n :if ([ :len \$Key ] > 0) do={ :set Return (\$Key . \":\")}\r\ + \n :if ([ :len \$Key ] > (\$Indent - 2)) do={:set Return (\$Return . \"\\\ + n\" . [ :pick \$Spaces 0 \$Indent ] . \$Value);\r\ + \n } else={:set Return (\$Return . [ :pick \$Spaces 0 (\$Indent - [ :len \ + \$Return ]) ] . \$Value)}\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set ParseCustom do={\r\ + \n :local Str [ :tostr \$1 ];\r\ + \n :local Start [:find [ \$Str ] [:tostr \$2]];\r\ + \n :local End [ :tostr \$3 ];\r\ + \n :local Num [ :tonum \$4 ];\r\ + \n :local Return;\r\ + \n :if ([:len \$Num] = 0) do={:set Num \"0\"}\r\ + \n :local rest [:pick \$Str (\$Start+\$Num) [:len \$Str]]; \r\ + \n :if ([:len \$End ] = 0) do={:set Return [:pick \$Str \$Num \$Start]} e\ + lse={\r\ + \n :set End [:find \$rest \$End];\r\ + \n :set Return [:pick \$rest 0 \$End];\r\ + \n }\r\ + \n:return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set LogPrint do={\r\ + \n :local Severity [ :tostr \$1 ];\r\ + \n :local Name [ :tostr \$2 ];\r\ + \n :local Message [ :tostr \$3 ];\r\ + \n :global PrintDebug;\r\ + \n :global PrintDebugOverride;\r\ + \n :global EitherOr;\r\ + \n :local Debug [ \$EitherOr (\$PrintDebugOverride->\$Name) \$PrintDebug \ + ];\r\ + \n :local PrintSeverity do={\r\ + \n :global TerminalColorOutput;\r\ + \n :if (\$TerminalColorOutput != true) do={:return \$1}\r\ + \n :local Color { debug=96; info=97; warning=93; error=91 };\r\ + \n :return (\"\\1B[\" . \$Color->\$1 . \"m\" . \$1 . \"\\1B[0m\");\r\ + \n}\r\ + \n :local Log ([ \$EitherOr \$Name \"\" ] . \": \" . \$Message);\ + \r\ + \n :if (\$Severity ~ (\"^(custom|debug|error|info)\\\$\")) do={\r\ + \n :if (\$Severity = \"debug\") do={:log debug \$Log}\r\ + \n :if (\$Severity = \"error\") do={:log error \$Log}\r\ + \n :if (\$Severity = \"info\" ) do={:log info \$Log}\r\ + \n } else={:log warning \$Log; :set Severity \"warning\"}\r\ + \n :if (\$Severity != \"debug\" || \$Debug = true) do={:put ([ \$PrintSev\ + erity \$Severity ] . \": \" . \$Message)}\r\ + \n}\r\ + \n\r\ + \n:set CertificateAvailable do={\r\ + \n :local CommonName [ :tostr \$1 ];\r\ + \n :local UseFor [ :tostr \$2 ];\r\ + \n\r\ + \n :global CertificateDownload;\r\ + \n :global ParseKeyValueStore;\r\ + \n\r\ + \n :if ([ :len \$UseFor ] = 0) do={\r\ + \n :log warning (\$0 . \"The intended use is undefined!\");\r\ + \n :set UseFor \"undefined\";\r\ + \n }\r\ + \n :if ([ /system/resource/get free-hdd-space ] < 8388608 && \\\r\ + \n [ /certificate/settings/get crl-download ] = true && \\\r\ + \n [ /certificate/settings/get crl-store ] = \"system\") do={\r\ + \n :log warning (\$0 . \"This system has low free flash space but \" . \ + \\\r\ + \n \"is configured to download certificate CRLs to system!\");\r\ + \n }\r\ + \n :if ([ :len \$CommonName ] = 0) do={\r\ + \n :log warning (\$0 . \"No CommonName given!\");\r\ + \n :return false;\r\ + \n }\r\ + \n :local CertSettings [ /certificate/settings/get ];\r\ + \n :if (((\$CertSettings->\"builtin-trust-anchors\") = \"trusted\" || \\\ + \r\ + \n (\$CertSettings->\"builtin-trust-store\") ~ \$UseFor || \\\r\ + \n (\$CertSettings->\"builtin-trust-store\") = \"all\") && \\\r\ + \n [ :len [ /certificate/builtin/find where common-name=\$CommonName\ + \_] ] > 0) do={\r\ + \n :return true;\r\ + \n }\r\ + \n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] = 0\ + ) do={\r\ + \n :log info (\$0 . \"Certificate with CommonName '\" . \$CommonName . \ + \"' not available.\");\r\ + \n :if ([ \$CertificateDownload \$CommonName ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] > 1\ + ) do={\r\ + \n :log info (\$0 . \"There are \" . \$CertCount . \" Certificates with\ + \_CommonName '\" . \$CommonName . \"'. Should be ok.\");\r\ + \n :return true;\r\ + \n }\r\ + \n :local CertVal [ /certificate/get [ find where common-name=\$CommonNam\ + e ] ];\r\ + \n :while ((\$CertVal->\"akid\") != \"\" && (\$CertVal->\"akid\") != (\$C\ + ertVal->\"skid\")) do={\r\ + \n :if ([ :len [ /certificate/find where skid=(\$CertVal->\"akid\") ] ]\ + \_= 0) do={\r\ + \n :local IssuerCN ([ \$ParseKeyValueStore (\$CertVal->\"issuer\") ]-\ + >\"CN\");\r\ + \n :log info (\$0 . \"Certificate chain for '\" . \$CommonName . \"' \ + is incomplete, missing '\" . \$IssuerCN . \"'.\");\r\ + \n :if ([ \$CertificateDownload \$IssuerCN ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n :set CertVal [ /certificate/get [ find where skid=(\$CertVal->\"akid\ + \") ] ];\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set CertificateDownload do={\r\ + \n :local CommonName [ :tostr \$1 ];\r\ + \n :global ScriptUpdatesBaseUrl;\r\ + \n :global ScriptUpdatesUrlSuffix;\r\ + \n :global CertificateNameByCN;\r\ + \n :global CleanName;\r\ + \n :global FetchUserAgentStr;\r\ + \n :global RmFile;\r\ + \n :global WaitForFile;\r\ + \n\r\ + \n :log info (\$0 . \"Downloading and importing certificate with \" . \\\ + \r\ + \n \"CommonName '\" . \$CommonName . \"'.\");\r\ + \n :local FileName ([ \$CleanName \$CommonName ] . \".pem\");\r\ + \n :do {\r\ + \n /tool/fetch check-certificate=yes-without-crl mode=https http-header\ + -field=({ [ \$FetchUserAgentStr \$0 ] }) \\\r\ + \n (\$ScriptUpdatesBaseUrl . \"certs/\" . \$FileName . \$ScriptUpdate\ + sUrlSuffix) \\\r\ + \n dst-path=\$FileName as-value;\r\ + \n \$WaitForFile \$FileName;\r\ + \n } on-error={\r\ + \n :log warning (\$0 . \" Failed downloading certificate with CommonNam\ + e '\" . \$CommonName . \\\r\ + \n \"' from repository! Trying fallback to mkcert.org...\");\r\ + \n :do {\r\ + \n :local CertSettings [ /certificate/settings/get ];\r\ + \n :if ([ :len [ /certificate/find where common-name=\"ISRG Root X1\"\ + \_] ] = 0 && \\\r\ + \n !(((\$CertSettings->\"builtin-trust-anchors\") = \"trusted\" \ + || \\\r\ + \n (\$CertSettings->\"builtin-trust-store\") ~ \"fetch\" || \ + \\\r\ + \n (\$CertSettings->\"builtin-trust-store\") = \"all\") && \\\ + \r\ + \n [ :len [ /certificate/builtin/find where common-name=\"ISRG\ + \_Root X1\" ] ] > 0)) do={\r\ + \n :log error (\$0 . \" Required certificate is not available.\");\ + \r\ + \n :return false;\r\ + \n }\r\ + \n /tool/fetch check-certificate=yes-without-crl mode=https http-head\ + er-field=({ [ \$FetchUserAgentStr \$0 ] }) \\\r\ + \n url=\"https://mkcert.org/generate/\" http-data=[ :serialize to=jso\ + n ({ \$CommonName }) ] \\\r\ + \n dst-path=\$FileName as-value;\r\ + \n \$WaitForFile \$FileName;\r\ + \n :if ([ /file/get \$FileName size ] = 0) do={\r\ + \n \$RmFile \$FileName;\r\ + \n :error false;\r\ + \n }\r\ + \n } on-error={\r\ + \n :log error (\$0 . \" Failed downloading certificate with CommonNam\ + e '\" . \$CommonName . \"'!\");\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n /certificate/import file-name=\$FileName passphrase=\"\" as-value;\r\ + \n :delay 1s;\r\ + \n \$RmFile \$FileName;\r\ + \n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] = 0\ + ) do={\r\ + \n /certificate/remove [ find where name~(\"^\" . \$FileName . \"_[0-9]\ + +\\\$\") ];\r\ + \n :log warning (\$0 . \" Certificate with CommonName '\" . \$CommonNam\ + e . \"' still unavailable!\");\r\ + \n :return false;\r\ + \n }\r\ + \n :foreach Cert in=[ /certificate/find where name~(\"^\" . \$FileName . \ + \"_[0-9]+\\\$\") ] do={\r\ + \n \$CertificateNameByCN [ /certificate/get \$Cert common-name ];\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set CertificateNameByCN do={\r\ + \n :local Match [ :tostr \$1 ];\r\ + \n :global CleanName;\r\ + \n :local Cert ([ /certificate/find where (common-name=\$Match or fingerp\ + rint=\$Match or name=\$Match) ]->0);\r\ + \n :if ([ :len \$Cert ] = 0) do={\r\ + \n :log warning (\$0 .\" No matching certificate found.\");\r\ + \n :return false;\r\ + \n }\r\ + \n :local CommonName [ /certificate/get \$Cert common-name ];\r\ + \n /certificate/set \$Cert name=[ \$CleanName \$CommonName ];\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set CharacterMultiply do={\r\ + \n :local Str [ :tostr \$1 ];\r\ + \n :local Num [ :tonum \$2 ];\r\ + \n :if (\$Num = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :for I from=1 to=\$Num do={:set Return (\$Return . \$Str)}\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set FileExists do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :local Type [ :tostr \$2 ];\r\ + \n :global FileGet;\r\ + \n :local FileVal [ \$FileGet \$FileName ];\r\ + \n :if (\$FileVal = false) do={ :return false; }\r\ + \n :if ([ :len (\$FileVal->\"size\") ] = 0) do={ :return false; }\r\ + \n :if ([ :len \$Type ] = 0 || \$FileVal->\"type\" = \$Type) do={ :return\ + \_true; }\r\ + \n :return false;\r\ + \n}\r\ + \n\r\ + \n:set Grep do={\r\ + \n :local Input ([ :tostr \$1 ] . \"\\n\");\r\ + \n :local Pattern [ :tostr \$2 ];\r\ + \n :if ([ :typeof [ :find \$Input \$Pattern ] ] = \"nil\") do={:return []\ + }\r\ + \n :do {\r\ + \n :local Line [ :pick \$Input 0 [ :find \$Input \"\\n\" ] ];\r\ + \n :if ([ :typeof [ :find \$Line \$Pattern ] ] = \"num\") do={:return \ + \$Line}\r\ + \n :set Input [ :pick \$Input ([ :find \$Input \"\\n\" ] + 1) [ :len \$\ + Input ] ];\r\ + \n } while=([ :len \$Input ] > 0);\r\ + \n :return [];\r\ + \n}\r\ + \n\r\ + \n:set HumanReadableNum do={\r\ + \n :local Input [ :tonum \$1 ];\r\ + \n :local Base [ :tonum \$2 ];\r\ + \n :global EitherOr;\r\ + \n :global IfThenElse;\r\ + \n :local Prefix \"kMGTPE\";\r\ + \n :local Pow 1;\r\ + \n :set Base [ \$EitherOr \$Base 1024 ];\r\ + \n :local Bin [ \$IfThenElse (\$Base = 1024) \"i\" \"\" ];\r\ + \n :if (\$Input < \$Base) do={:return \$Input}\r\ + \n :for I from=0 to=[ :len \$Prefix ] do={\r\ + \n :set Pow (\$Pow * \$Base);\r\ + \n :if (\$Input / \$Base < \$Pow) do={\r\ + \n :set Prefix [ :pick \$Prefix \$I ];\r\ + \n :local Tmp1 (\$Input * 100 / \$Pow);\r\ + \n :local Tmp2 (\$Tmp1 / 100);\r\ + \n :if (\$Tmp2 >= 100) do={:return (\$Tmp2 . \$Prefix . \$Bin)}\r\ + \n :return (\$Tmp2 . \".\" . \\\r\ + \n [ :pick \$Tmp1 [ :len \$Tmp2 ] ([ :len \$Tmp1 ] - [ :len \$Tmp\ + 2 ] + 1) ] . \\\r\ + \n \$Prefix . \$Bin);\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set CleanFilePath do={\r\ + \n :local Path [ :tostr \$1 ];\r\ + \n :global CharacterReplace;\r\ + \n :while (\$Path ~ \"//\") do={ :set \$Path [ \$CharacterReplace \$Path \ + \"//\" \"/\" ]; }\r\ + \n :if ([ :pick \$Path 0 ] = \"/\") do={ :set Path [ :pick \$Path 1 [ :le\ + n \$Path ] ]; }\r\ + \n :if ([ :pick \$Path ([ :len \$Path ] - 1) ] = \"/\") do={ :set Path [ \ + :pick \$Path 0 ([ :len \$Path ] - 1) ]; }\r\ + \n :return \$Path;\r\ + \n}\r\ + \n\r\ + \n:set CleanName do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :local Return \"\";\r\ + \n :for I from=0 to=([ :len \$Input ] - 1) do={\r\ + \n :local Char [ :pick \$Input \$I ];\r\ + \n :if ([ :typeof [ find \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr\ + stuvwxyz0123456789\" \$Char ] ] = \"nil\") do={\r\ + \n :do {:if ([ :len \$Return ] = 0) do={:error true};\r\ + \n :if ([ :pick \$Return ([ :len \$Return ] - 1) ] = \"-\") do={:er\ + ror true}; :set Char \"-\"} on-error={:set Char \"\"}; \r\ + \n };\r\ + \n :set Return (\$Return . \$Char)};\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set RandomDelay do={\r\ + \n :local Time [ :tonum \$1 ];\r\ + \n :local Unit [ :tostr \$2 ];\r\ + \n :global EitherOr;\r\ + \n :global GetRandomNumber;\r\ + \n :global MAX;\r\ + \n :if (\$Time = 0) do={:return false}\r\ + \n :delay ([ \$MAX 10 [ \$GetRandomNumber ([ :tonsec [ :totime (\$Time . \ + [ \$EitherOr \$Unit \"s\" ]) ] ] / 1000000) ] ] . \"ms\");\r\ + \n}\r\ + \n\r\ + \n:set EscapeForRegEx do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :if ([ :len \$Input ] = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :local Chars (\"^.[]\\\$()|*+\?{}\\\\\");\r\ + \n :for I from=0 to=([ :len \$Input ] - 1) do={\r\ + \n :local Char [ :pick \$Input \$I ];\r\ + \n :if ([ :find \$Chars \$Char ]) do={:set Char (\"\\\\\" . \$Char)}\r\ + \n :set Return (\$Return . \$Char);\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set ExitError do={\r\ + \n :local ExitOK [ :tostr \$1 ];\r\ + \n :local Name [ :tostr \$2 ];\r\ + \n :local Error [ :tostr \$3 ];\r\ + \n :global IfThenElse;\r\ + \n :if (\$ExitOK = \"false\") do={\r\ + \n :log error (\$Name [ \$IfThenElse ([ :pick \$Name 0 1 ] = \"\\\$\") \ + \\\r\ + \n \"Function\" \"Script\" ] . \" '\" . \$Name . \"' exited with er\ + ror\" . \\\r\ + \n [ \$IfThenElse (!(\$Error ~ \"^(|true|false)\\\$\")) (\": \" . \ + \$Error) \".\" ]);\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set ExitOnError do={\r\ + \n :local Name [ :tostr \$1 ];\r\ + \n :local Error [ :tostr \$2 ];\r\ + \n :global IfThenElse;\r\ + \n :log error (\$Name . \" \" . [ \$IfThenElse ([ :pick \$Name 0 1 ] = \"\ + \\\$\") \\\r\ + \n \"Function\" \"Script\" ] . \" '\" . \$Name . \"' exited with erro\ + r\" . \\\r\ + \n [ \$IfThenElse (!(\$Error ~ \"^(|true|false)\\\$\")) (\": \" . \$E\ + rror) \".\" ]);\r\ + \n}\r\ + \n\r\ + \n:set MiniDateTimeStamp do={\r\ + \n :local mikro [ :tobool \$1 ];\r\ + \n /system/clock;\r\ + \n :local vdate [get date];\r\ + \n :local vtime [get time];\r\ + \n :local vdoff [:toarray \"0,4,5,7,8,10\"];\r\ + \n :local MM [:pick \$vdate (\$vdoff->2) (\$vdoff->3)];\r\ + \n :local M [:tonum \$MM];\r\ + \n :if (\$vdate ~ \".../../....\") do={\r\ + \n :set vdoff [:toarray \"7,11,1,3,4,6\"];\r\ + \n :set M ([:find \"xxanebarprayunulugepctovecANEBARPRAYUNULUGE\ + PCTOVEC\" [:pick \$vdate (\$vdoff->2) (\$vdoff->3)] -1] / 2);\r\ + \n :if (\$M>12) do={:set M (\$M - 12)}\r\ + \n :set MM [:pick (100 + \$M) 1 3];\r\ + \n }\r\ + \n :local yyyy [:pick \$vdate (\$vdoff->0) (\$vdoff->1)];\r\ + \n :local dd [:pick \$vdate (\$vdoff->4) (\$vdoff->5)];\r\ + \n :local HH [:pick \$vtime 0 2];\r\ + \n :local mm [:pick \$vtime 3 5];\r\ + \n :local ss [:pick \$vtime 6 8];\r\ + \n :local Return;\r\ + \n :if (\$mikro = true) do={\r\ + \n :set Return ([:pick \$yyyy 2 4] . [:pick \$dd 0 1] . [:pick \$HH 0\ + \_1] . [:pick \$mm 3 4] . [:pick \$ss 6 7]);\r\ + \n } else={\r\ + \n :set Return ([\$yyyy] . [\$MM] . [\$dd] . [\$HH] . [\$mm] . [\$ss]);\ + \r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set WaitForFile do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :local WaitTime [ :totime \$2 ];\r\ + \n :global CleanFilePath;\r\ + \n :global EitherOr;\r\ + \n :global MAX;\r\ + \n :set FileName [ \$CleanFilePath \$FileName ];\r\ + \n :local Delay ([ \$MAX [ \$EitherOr \$WaitTime 2s ] 100ms ] / 9);\r\ + \n :do {\r\ + \n :retry {\r\ + \n /file/get \$FileName;\r\ + \n :return true;\r\ + \n } delay=\$Delay max=10;\r\ + \n } on-error={ }\r\ + \n :while ([ :len [ /file/find where name=\$FileName ] ] > 0) do={\r\ + \n :do {\r\ + \n /file/get \$FileName;\r\ + \n :return true;\r\ + \n } on-error={ }\r\ + \n :delay \$Delay;\r\ + \n :set Delay (\$Delay * 3 / 2);\r\ + \n }\r\ + \n :return false;\r\ + \n}\r\ + \n\r\ + \n:set FileGet do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :global WaitForFile;\r\ + \n :if ([ \$WaitForFile \$FileName 0s ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n :local FileVal false;\r\ + \n :do {\r\ + \n :set FileVal [ /file/get \$FileName ];\r\ + \n } on-error={ }\r\ + \n :return \$FileVal;\r\ + \n}\r\ + \n\r\ + \n:set RmDir do={\r\ + \n :local DirName [ :tostr \$1 ];\r\ + \n :global FileGet;\r\ + \n :log debug (\$0 . \" Removing directory: \". \$DirName);\r\ + \n :local DirVal [ \$FileGet \$DirName ];\r\ + \n :if (\$DirVal = false) do={\r\ + \n :log debug (\$0 . \" ... which does not exist.\");\r\ + \n :return true;\r\ + \n };\r\ + \n :if (\$DirVal->\"type\" != \"directory\") do={\r\ + \n :log error (\$0 . \" Directory '\" . \$DirName . \"' is not a direct\ + ory.\");\r\ + \n :return false;\r\ + \n };\r\ + \n :onerror Err {\r\ + \n /file/remove \$DirName;\r\ + \n } do={\r\ + \n :log error (\$0 . \" Removing directory '\" . \$DirName . \"' failed\ + : \" . \$Err);\r\ + \n :return false;\r\ + \n };\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set RmFile do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :global FileGet;\r\ + \n :log debug (\$0 . \" Removing file: \". \$FileName);\r\ + \n :local FileVal [ \$FileGet \$FileName ];\r\ + \n :if (\$FileVal = false) do={\r\ + \n :log debug (\$0 . \" ... which does not exist.\");\r\ + \n :return true;\r\ + \n }\r\ + \n :if (\$FileVal->\"type\" = \"directory\" || \$FileVal->\"type\" = \"di\ + sk\") do={\r\ + \n :log error (\$0 . \" File '\" . \$FileName . \"' is not a file.\");\ + \r\ + \n :return false;\r\ + \n }\r\ + \n :onerror Err {\r\ + \n /file/remove \$FileName;\r\ + \n } do={\r\ + \n :log error (\$0 . \" Removing file '\" . \$FileName . \"' failed: \"\ + \_. \$Err); :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set MkDir do={\r\ + \n :local Path [ :tostr \$1 ];\r\ + \n :global CleanFilePath;\r\ + \n :global FileGet;\r\ + \n :global RmDir;\r\ + \n :global WaitForFile;\r\ + \n\r\ + \n :local MkTmpfs do={\r\ + \n :global WaitForFile;\r\ + \n :local TmpFs [ /disk/find where slot=tmpfs type=tmpfs ];\r\ + \n :if ([ :len \$TmpFs ] = 1) do={\r\ + \n :if ([ /disk/get \$TmpFs disabled ] = true) do={\r\ + \n :log info (\$0 . \" The tmpfs is disabled, enabling.\");\r\ + \n /disk/enable \$TmpFs;\r\ + \n }\r\ + \n :return true;\r\ + \n }\r\ + \n :log info (\$0 . \" Creating disk of type tmpfs.\");\r\ + \n \$RmDir \"tmpfs\";\r\ + \n :onerror Err {\r\ + \n /disk/add slot=tmpfs type=tmpfs tmpfs-max-size=([ /system/resource\ + /get total-memory ] / 3);\r\ + \n \$WaitForFile \"tmpfs\";\r\ + \n } do={\r\ + \n :log warning (\$0 . \" Creating disk of type tmpfs failed: \" . \$\ + Err);\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n }\r\ + \n :set Path [ \$CleanFilePath \$Path ];\r\ + \n :if (\$Path = \"\") do={\r\ + \n :return true;\r\ + \n }\r\ + \n :log debug (\$0 . \" Making directory: \" . \$Path);\r\ + \n :local PathVal [ \$FileGet \$Path ];\r\ + \n :if (\$PathVal->\"type\" = \"directory\") do={\r\ + \n :log debug (\$0 . \" ... which already exists.\");\r\ + \n :return true;\r\ + \n }\r\ + \n :if ([ :pick \$Path 0 5 ] = \"tmpfs\") do={\r\ + \n :if ([ \$MkTmpfs ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n :onerror Err {\r\ + \n /file/add type=\"directory\" name=\$Path;\r\ + \n \$WaitForFile \$Path;\r\ + \n } do={\r\ + \n :log warning (\$0 . \" Making directory '\" . \$Path . \"' failed: \ + \" . \$Err);\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set MAX do={:if (\$1 > \$2) do={:return \$1}\r\ + \n :return \$2;\r\ + \n}\r\ + \n:set MIN do={:if (\$1 < \$2) do={:return \$1}\r\ + \n :return \$2;\r\ + \n}\r\ + \n\r\ + \n:set CharacterMultiply do={\r\ + \n :local Str [ :tostr \$1 ];\r\ + \n :local Num [ :tonum \$2 ];\r\ + \n :if (\$Num = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :for I from=1 to=\$Num do={:set Return (\$Return . \$Str)}\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set CharacterReplace do={\r\ + \n :local String [ :tostr \$1 ];\r\ + \n :local ReplaceFrom [ :tostr \$2 ];\r\ + \n :local ReplaceWith [ :tostr \$3 ];\r\ + \n :local Return \"\";\r\ + \n :if (\$ReplaceFrom = \"\") do={:return \$String}\r\ + \n :while ([ :typeof [ :find \$String \$ReplaceFrom ] ] != \"nil\") do={\ + \r\ + \n :local Pos [ :find \$String \$ReplaceFrom ];\r\ + \n :set Return (\$Return . [ :pick \$String 0 \$Pos ] . \$ReplaceWith);\ + \r\ + \n :set String [ :pick \$String (\$Pos + [ :len \$ReplaceFrom ]) [ :len\ + \_\$String ] ];\r\ + \n }\r\ + \n :return (\$Return . \$String);\r\ + \n}\r\ + \n\r\ + \n:set IfThenElse do={\r\ + \n :if ([ :tostr \$1 ] = \"true\" || [ :tobool \$1 ] = true) do={:return \ + \$2};\r\ + \n :return \$3;\r\ + \n}\r\ + \n\r\ + \n:set EitherOr do={\r\ + \n :global IfThenElse;\r\ + \n :if ([ :typeof \$1 ] = \"num\") do={:return [ \$IfThenElse (\$1 != 0) \ + \$1 \$2 ]}\r\ + \n :if ([ :typeof \$1 ] = \"time\") do={:return [ \$IfThenElse (\$1 > 0s)\ + \_\$1 \$2 ]}\r\ + \n# this works for boolean values, literal ones with parentheses\r\ + \n :return [ \$IfThenElse ([ :len [ :tostr \$1 ] ] > 0) \$1 \$2 ];\r\ + \n}\r\ + \n\r\ + \n:set ParseKeyValueStore do={\r\ + \n :local Source \$1;\r\ + \n :if ([ :pick \$Source 0 1 ] = \"{\") do={\r\ + \n :do {\r\ + \n :return [ :deserialize from=json \$Source ];\r\ + \n } on-error={ }\r\ + \n } \r\ + \n :if ([ :typeof \$Source ] != \"array\") do={:set Source [ :tostr \$1 ]\ + }\r\ + \n :local Result ({});\r\ + \n :foreach KeyValue in=[ :toarray \$Source ] do={\r\ + \n :if ([ :find \$KeyValue \"=\" ]) do={\r\ + \n :local Key [ :pick \$KeyValue 0 [ :find \$KeyValue \"=\" ] ];\r\ + \n :local Value [ :pick \$KeyValue ([ :find \$KeyValue \"=\" ] + 1) [\ + \_:len \$KeyValue ] ];\r\ + \n :if (\$Value=\"true\") do={:set Value true}\r\ + \n :if (\$Value=\"false\") do={:set Value false}\r\ + \n :set (\$Result->\$Key) \$Value;\r\ + \n } else={:set (\$Result->\$KeyValue) true}\r\ + \n }\r\ + \n :return \$Result;\r\ + \n}\r\ + \n:set RequiredRouterOS do={\r\ + \n :local Caller [ :tostr \$1 ];\r\ + \n :local Required [ :tostr \$2 ];\r\ + \n :local Warn [ :tostr \$3 ];\r\ + \n :global IfThenElse;\r\ + \n :global VersionToNum;\r\ + \n :if (!(\$Required ~ \"^\\\\d+\\\\.\\\\d+((alpha|beta|rc|\\\\.)\\\\d+|)\ + \\\$\")) do={\r\ + \n :log error (\$0 . \" No valid RouterOS version: \" . \$Required);\r\ + \n :return false;\r\ + \n }\r\ + \n :if ([ \$VersionToNum \$Required ] > [ \$VersionToNum [ /system/packag\ + e/update/get installed-version ] ]) do={\r\ + \n :if (\$Warn = \"true\") do={\r\ + \n :log warning (\$0 . \" This \" . [ \$IfThenElse ([ :pick \$Caller \ + 0 ] = (\"\\\$\")) \"function\" \"script\" ] . \\\r\ + \n \" '\" . \$Caller . \"' (at least specific functionality) requir\ + es RouterOS \" . \$Required . \". Please update!\");\r\ + \n }\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set ValidateSyntax do={\r\ + \n :local Code [ :tostr \$1 ];\r\ + \n :global LogPrint;\r\ + \n :onerror Err {\r\ + \n [ :parse (\":local Validate do={\\n\" . \$Code . \"\\n}\") ];\r\ + \n } do={\r\ + \n \$LogPrint debug \$0 (\"Valdation failed: \" . \$Err);\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set VersionToNum do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :local Multi 0x1000000;\r\ + \n :local Return 0;\r\ + \n :global CharacterReplace;\r\ + \n :set Input [ \$CharacterReplace \$Input \".\" \",\" ];\r\ + \n :foreach I in={ \"zero\"; \"alpha\"; \"beta\"; \"rc\" } do={\r\ + \n :set Input [ \$CharacterReplace \$Input \$I (\",\" . \$I . \",\") ];\ + \r\ + \n }\r\ + \n :foreach Value in=([ :toarray \$Input ], 0) do={\r\ + \n :local Num [ :tonum \$Value ];\r\ + \n :if (\$Multi = 0x100) do={\r\ + \n :if ([ :typeof \$Num ] = \"num\") do={\r\ + \n :set Return (\$Return + 0xff00);\r\ + \n :set Multi (\$Multi / 0x100);\r\ + \n } else={\r\ + \n :if (\$Value = \"zero\") do={ }\r\ + \n :if (\$Value = \"alpha\") do={ :set Return (\$Return + 0x3f00); \ + }\r\ + \n :if (\$Value = \"beta\") do={ :set Return (\$Return + 0x5f00); }\ + \r\ + \n :if (\$Value = \"rc\") do={ :set Return (\$Return + 0x7f00); }\r\ + \n }\r\ + \n }\r\ + \n :if ([ :typeof \$Num ] = \"num\") do={ :set Return (\$Return + (\$Va\ + lue * \$Multi)); }\r\ + \n :set Multi (\$Multi / 0x100);\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set ScriptLock do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local WaitMax [ :totime \$2 ];\r\ + \n :global GetRandom20CharAlNum;\r\ + \n :global IfThenElse;\r\ + \n :global ScriptLockOrder;\r\ + \n :if ([ :typeof \$ScriptLockOrder ] = \"nothing\") do={\r\ + \n :set ScriptLockOrder ({});\r\ + \n }\r\ + \n :if ([ :typeof (\$ScriptLockOrder->\$Script) ] = \"nothing\") do={\r\ + \n :set (\$ScriptLockOrder->\$Script) ({});\r\ + \n }\r\ + \n :local JobCount do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :return [ :len [ /system/script/job/find where script=\$Script ] ];\ + \r\ + \n }\r\ + \n :local TicketCount do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :local Count 0;\r\ + \n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :if ([ :typeof \$Ticket ] != \"nothing\") do={:set Count (\$Count \ + + 1)}\r\ + \n }\r\ + \n :return \$Count;\r\ + \n }\r\ + \n :local IsFirstTicket do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local Check [ :tostr \$2 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :if (\$Ticket = \$Check) do={:return true}\r\ + \n :if ([ :typeof \$Ticket ] != \"nothing\" && \$Ticket != \$Check) d\ + o={:return false}\r\ + \n }\r\ + \n :return false;\r\ + \n }\r\ + \n :local AddTicket do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local Add [ :tostr \$2 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :while (true) do={\r\ + \n :local Pos [ :len (\$ScriptLockOrder->\$Script) ];\r\ + \n :set (\$ScriptLockOrder->\$Script->\$Pos) \$Add;\r\ + \n :delay 10ms;\r\ + \n :if ((\$ScriptLockOrder->\$Script->\$Pos) = \$Add) do={:return tru\ + e}\r\ + \n }\r\ + \n }\r\ + \n :local RemoveTicket do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local Remove [ :tostr \$2 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :foreach Id,Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :while ((\$ScriptLockOrder->\$Script->\$Id) = \$Remove) do={\r\ + \n :set (\$ScriptLockOrder->\$Script->\$Id);\r\ + \n :delay 10ms;\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n :local CleanupTickets do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :if ([ :typeof \$Ticket ] != \"nothing\") do={:return false}\r\ + \n }\r\ + \n :set (\$ScriptLockOrder->\$Script) ({});\r\ + \n }\r\ + \n :if ([ :typeof \$WaitMax ] = \"nil\" ) do={:set WaitMax 0s}\r\ + \n :if ([ :len [ /system/script/find where name=\$Script ] ] = 0) do={\r\ + \n :log error (\$0 . \" A script named '\" . \$Script . \"' does not ex\ + ist!\");\r\ + \n :error false;\r\ + \n }\r\ + \n :if ([ \$JobCount \$Script ] = 0) do={\r\ + \n \$LogPrint error \$0 (\"No script '\" . \$Script . \"' is running!\"\ + );\r\ + \n :error false;\r\ + \n }\r\ + \n :if ([ \$TicketCount \$Script ] >= [ \$JobCount \$Script ]) do={\r\ + \n :log error (\$0 . \" More tickets than running scripts '\" . \$Scrip\ + t . \"', resetting!\");\r\ + \n :set (\$ScriptLockOrder->\$Script) ({});\r\ + \n /system/script/job/remove [ find where script=\$Script ];\r\ + \n }\r\ + \n :local MyTicket [ \$GetRandom20CharAlNum 6 ];\r\ + \n \$AddTicket \$Script \$MyTicket;\r\ + \n :local WaitInterval (\$WaitMax / 20);\r\ + \n :local WaitTime \$WaitMax;\r\ + \n :while (\$WaitTime > 0 && \\\r\ + \n ([ \$IsFirstTicket \$Script \$MyTicket ] = false || \\\r\ + \n [ \$TicketCount \$Script ] < [ \$JobCount \$Script ])) do={\r\ + \n :set WaitTime (\$WaitTime - \$WaitInterval);\r\ + \n :delay \$WaitInterval;\r\ + \n }\r\ + \n :if ([ \$IsFirstTicket \$Script \$MyTicket ] = true && \\\r\ + \n [ \$TicketCount \$Script ] = [ \$JobCount \$Script ]) do={\r\ + \n \$RemoveTicket \$Script \$MyTicket;\r\ + \n \$CleanupTickets \$Script;\r\ + \n :return true;\r\ + \n }\r\ + \n \$RemoveTicket \$Script \$MyTicket;\r\ + \n \$LogPrint debug \$0 (\"Script '\" . \$Script . \"' started more than \ + once\" . \\\r\ + \n [ \$IfThenElse (\$WaitTime < \$WaitMax) \" and timed out waiting for\ + \_lock\" \"\" ] . \"...\");\r\ + \n :return false;\r\ + \n}\r\ + \n\r\ + \n:set SendNotification do={ :onerror Err {\r\ + \n :global SendNotification2;\r\ + \n \$SendNotification2 ({ origin=\$0; subject=\$1; message=\$2; link=\$3;\ + \_silent=\$4 });\r\ + \n} do={:global ExitOnError; \$ExitOnError \$0 \$Err}\r\ + \n}\r\ + \n\r\ + \n:set SendNotification2 do={\r\ + \n :local Notification \$1;\r\ + \n :global NotificationFunctions;\r\ + \n :foreach FunctionName,Discard in=\$NotificationFunctions do={\r\ + \n (\$NotificationFunctions->\$FunctionName) (\"\\\$NotificationFunctio\ + ns->\\\"\" . \$FunctionName . \"\\\"\") \$Notification;\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set SymbolByUnicodeName do={\r\ + \n :local Name [ :tostr \$1 ];\r\ + \n :global EitherOr;\r\ + \n :global SymbolsExtra;\r\ + \n :local Symbols ({\r\ + \n \"abacus\"=\"\\F0\\9F\\A7\\AE\";\r\ + \n \"alarm-clock\"=\"\\E2\\8F\\B0\";\r\ + \n \"arrow-down\"=\"\\E2\\AC\\87\";\r\ + \n \"arrow-up\"=\"\\E2\\AC\\86\";\r\ + \n \"calendar\"=\"\\F0\\9F\\93\\85\";\r\ + \n \"card-file-box\"=\"\\F0\\9F\\97\\83\";\r\ + \n \"chart-decreasing\"=\"\\F0\\9F\\93\\89\";\r\ + \n \"chart-increasing\"=\"\\F0\\9F\\93\\88\";\r\ + \n \"cloud\"=\"\\E2\\98\\81\";\r\ + \n \"cross-mark\"=\"\\E2\\9D\\8C\";\r\ + \n \"earth\"=\"\\F0\\9F\\8C\\8D\";\r\ + \n \"fire\"=\"\\F0\\9F\\94\\A5\";\r\ + \n \"floppy-disk\"=\"\\F0\\9F\\92\\BE\";\r\ + \n \"gear\"=\"\\E2\\9A\\99\";\r\ + \n \"heart\"=\"\\E2\\99\\A5\";\r\ + \n \"high-voltage-sign\"=\"\\E2\\9A\\A1\";\r\ + \n \"incoming-envelope\"=\"\\F0\\9F\\93\\A8\";\r\ + \n \"information\"=\"\\E2\\84\\B9\";\r\ + \n \"large-orange-circle\"=\"\\F0\\9F\\9F\\A0\";\r\ + \n \"large-red-circle\"=\"\\F0\\9F\\94\\B4\";\r\ + \n \"link\"=\"\\F0\\9F\\94\\97\";\r\ + \n \"lock-with-ink-pen\"=\"\\F0\\9F\\94\\8F\";\r\ + \n \"memo\"=\"\\F0\\9F\\93\\9D\";\r\ + \n \"mobile-phone\"=\"\\F0\\9F\\93\\B1\";\r\ + \n \"pushpin\"=\"\\F0\\9F\\93\\8C\";\r\ + \n \"scissors\"=\"\\E2\\9C\\82\";\r\ + \n \"scroll\"=\"\\F0\\9F\\93\\9C\";\r\ + \n \"smiley-partying-face\"=\"\\F0\\9F\\A5\\B3\";\r\ + \n \"smiley-smiling-face\"=\"\\E2\\98\\BA\";\r\ + \n \"smiley-winking-face-with-tongue\"=\"\\F0\\9F\\98\\9C\";\r\ + \n \"sparkles\"=\"\\E2\\9C\\A8\";\r\ + \n \"speech-balloon\"=\"\\F0\\9F\\92\\AC\";\r\ + \n \"star\"=\"\\E2\\AD\\90\";\r\ + \n \"warning-sign\"=\"\\E2\\9A\\A0\";\r\ + \n \"white-heavy-check-mark\"=\"\\E2\\9C\\85\"\r\ + \n }, \$SymbolsExtra);\r\ + \n :local Magic [ :pick [ /system/clock/get date ] 4 10 ];\r\ + \n :local Special {\r\ + \n \"information-04-01\"=\"\\F0\\9F\\9A\\BB\";\r\ + \n \"large-orange-circle-04-01\"=\"\\F0\\9F\\8D\\8A\";\r\ + \n \"large-orange-circle-10-31\"=\"\\F0\\9F\\8E\\83\";\r\ + \n \"large-red-circle-04-01\"=\"\\F0\\9F\\8D\\92\" };\r\ + \n :if ([ :len (\$Symbols->\$Name) ] = 0) do={\r\ + \n :log warning (\$0 . \"No symbol available for name '\" . \$Name . \"\ + '!\");\r\ + \n :return \"\";\r\ + \n }\r\ + \n :return ([ \$EitherOr (\$Special->(\$Name . \$Magic)) (\$Symbols->\$Na\ + me) ] . \"\\EF\\B8\\8F\");\r\ + \n}\r\ + \n\r\ + \n:set SymbolForNotification do={\r\ + \n :global NotificationsWithSymbols;\r\ + \n :global SymbolByUnicodeName;\r\ + \n :global IfThenElse;\r\ + \n :if (\$NotificationsWithSymbols != true) do={:return [ \$IfThenElse ([\ + \_:len \$2 ] > 0) ([ :tostr \$2 ] . \" \") \"\" ]}\r\ + \n :local Return \"\";\r\ + \n :foreach Symbol in=[ :toarray \$1 ] do={:set Return (\$Return . [ \$Sy\ + mbolByUnicodeName \$Symbol ])}\r\ + \n :return (\$Return . \" \");\r\ + \n}\r\ + \n\r\ + \n:set Unix2Dos do={:return [ :tocrlf [ :tostr \$1 ] ]}\r\ + \n:set UrlEncode do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :if ([ :len \$Input ] = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :local Chars (\"\\n\\r !\\\"#\\\$%&'()*+,:;<=>\?@[\\\\]^`{|}~\");\r\ + \n :local Subs { \"%0A\"; \"%0D\"; \"%20\"; \"%21\"; \"%22\"; \"%23\"; \"\ + %24\"; \"%25\"; \"%26\"; \"%27\";\r\ + \n \"%28\"; \"%29\"; \"%2A\"; \"%2B\"; \"%2C\"; \"%3A\"; \"%3B\"; \ + \"%3C\"; \"%3D\"; \"%3E\"; \"%3F\";\r\ + \n \"%40\"; \"%5B\"; \"%5C\"; \"%5D\"; \"%5E\"; \"%60\"; \"%7B\"; \ + \"%7C\"; \"%7D\"; \"%7E\" };\r\ + \n :for I from=0 to=([ :len \$Input ] - 1) do={\r\ + \n :local Char [ :pick \$Input \$I ];\r\ + \n :local Replace [ :find \$Chars \$Char ];\r\ + \n :if ([ :typeof \$Replace ] = \"num\") do={:set Char (\$Subs->\$Repla\ + ce)}\r\ + \n :set Return (\$Return . \$Char);\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set IsDNSResolving do={\r\ + \n :do {:resolve \"api.telegram.org\"} on-error={:return false}\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set IsFullyConnected do={\r\ + \n :global IsDNSResolving;\r\ + \n :global IsTimeSync;\r\ + \n :if ([ \$IsDNSResolving ] = false) do={:return false};\r\ + \n :if ([ \$IsTimeSync ] = false) do={:return false};\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set IsTimeSync do={\r\ + \n :global IsTimeSyncCached;\r\ + \n :global IsTimeSyncResetNtp;\r\ + \n :if (\$IsTimeSyncCached = true) do={:return true}\r\ + \n :if ([ /system/ntp/client/get enabled ] = true) do={\r\ + \n :if ([ /system/ntp/client/get status ] = \"synchronized\") do={\r\ + \n :set IsTimeSyncCached true;\r\ + \n :return true;\r\ + \n }\r\ + \n :local Uptime [ /system/resource/get uptime ];\r\ + \n :if ([ :typeof \$IsTimeSyncResetNtp ] = \"nothing\") do={\r\ + \n :set IsTimeSyncResetNtp \$Uptime;\r\ + \n }\r\ + \n :if (\$Uptime - \$IsTimeSyncResetNtp < 3m) do={:return false}\r\ + \n :log warning \"\$0 The ntp client is configured, but did not sync.\"\ + ;\r\ + \n :set IsTimeSyncResetNtp \$Uptime;\r\ + \n /system/ntp/client/set enabled=no;\r\ + \n :delay 20ms;\r\ + \n /system/ntp/client/set enabled=yes;\r\ + \n :return false;\r\ + \n }\r\ + \n :if ([ /system/license/get ]->\"level\" = \"free\" || \\\r\ + \n [ /system/resource/get ]->\"board-name\" = \"x86\") do={\r\ + \n :log debug \"\$0 No ntp client configured, relying on RTC for CHR fr\ + ee license and x86.\";\r\ + \n :return true;\r\ + \n }\r\ + \n :if ([ /ip/cloud/get update-time ] = true) do={\r\ + \n :if ([ :typeof [ /ip/cloud/get public-address ] ] = \"ip\") do={\r\ + \n :set IsTimeSyncCached true;\r\ + \n :return true;\r\ + \n }\r\ + \n :return false;\r\ + \n }\r\ + \n :log debug \"\$0 No time source configured! Returning gracefully...\";\ + \r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set FetchHugeFiles do={\r\ + \n :local ScriptName [ :tostr \$1 ];\r\ + \n :local Url [ :tostr \$2 ];\r\ + \n :local CheckCert [ :tostr \$3 ];\r\ + \n\r\ + \n :global CleanName;\r\ + \n :global FetchUserAgentStr;\r\ + \n :global GetRandom20CharAlNum;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global MkDir;\r\ + \n :global RmDir;\r\ + \n :global RmFile;\r\ + \n :global WaitForFile;\r\ + \n\r\ + \n :set CheckCert [ \$IfThenElse (\$CheckCert = \"false\") \"no\" \"yes-w\ + ithout-crl\" ];\r\ + \n\r\ + \n :local DirName (\"tmpfs/\" . [ \$CleanName \$ScriptName ]);\r\ + \n :if ([ \$MkDir \$DirName ] = false) do={\r\ + \n \$LogPrint error \$0 (\"Failed creating directory!\");\r\ + \n :return false;\r\ + \n }\r\ + \n\r\ + \n :local FileName (\$DirName . \"/\" . [ \$CleanName \$0 ] . \"-\" . [ \ + \$GetRandom20CharAlNum ]);\r\ + \n :onerror Err {\r\ + \n /tool/fetch check-certificate=\$CheckCert \$Url dst-path=\$FileName \ + \\\r\ + \n http-header-field=({ [ \$FetchUserAgentStr \$ScriptName ] }) as-va\ + lue;\r\ + \n } do={\r\ + \n :if ([ \$WaitForFile \$FileName 500ms ] = true) do={\r\ + \n \$RmFile \$FileName;\r\ + \n }\r\ + \n \$LogPrint debug \$0 (\"Failed downloading from \" . \$Url . \" - \"\ + \_. \$Err);\r\ + \n \$RmDir \$DirName;\r\ + \n :return false;\r\ + \n }\r\ + \n\r\ + \n :if ([ \$WaitForFile \$FileName 5s ] = false) do={\r\ + \n \$LogPrint debug \$0 (\"The file downloaded from \" . \$Url . \" did\ + \_not show up.\");\r\ + \n :return false;\r\ + \n }\r\ + \n\r\ + \n :local FileSize [ /file/get \$FileName size ];\r\ + \n :local Return \"\";\r\ + \n :local VarSize 0;\r\ + \n :while (\$VarSize != \$FileSize) do={\r\ + \n :set Return (\$Return . ([ /file/read offset=\$VarSize chunk-size=32\ + 768 file=\$FileName as-value ]->\"data\"));\r\ + \n :set FileSize [ /file/get \$FileName size ];\r\ + \n :set VarSize [ :len \$Return ];\r\ + \n :if (\$VarSize > \$FileSize) do={\r\ + \n :delay 100ms;\r\ + \n }\r\ + \n }\r\ + \n \$RmDir \$DirName;\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set FetchUserAgentStr do={\r\ + \n :local Caller [ :tostr \$1 ];\r\ + \n :global GlobalScriptsUrl;\r\ + \n :global CommitBrief;\r\ + \n :global IfThenElse;\r\ + \n :local Resource [ /system/resource/get ];\r\ + \n :return (\"User-Agent: Mikrotik/\" . \$Resource->\"version\" . \" \" .\ + \_\$Resource->\"architecture-name\" . \\\r\ + \n \" \" . \$Caller . \"/Fetch (\" . \$GlobalScriptsUrl . \"; \" . [ \$\ + CommitBrief ] . \")\");\r\ + \n}\r\ + \n\r\ + \n:set ScriptInstallUpdate do={ :onerror Err {\r\ + \n :local Scripts [ :toarray \$1 ];\r\ + \n :local NewComment [ :tostr \$2 ];\r\ + \n :global InitSetup;\r\ + \n\r\ + \n :global CommitId;\r\ + \n :global ScriptUpdatesCRLF;\r\ + \n :global GlobalConfVersion;\r\ + \n :global Identity;\r\ + \n :global CertificateAvailable;\r\ + \n :global CheckSumsVerify;\r\ + \n :global GlobalScriptsUrl;\r\ + \n :global SumsDirAndFileName;\r\ + \n :global GlobalScriptsUrlSuffix;\r\ + \n :global EitherOr;\r\ + \n :global FetchUserAgentStr;\r\ + \n :global Grep;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global ParseKeyValueStore;\r\ + \n :global RequiredRouterOS;\r\ + \n :global SendNotification2;\r\ + \n :global SymbolForNotification;\r\ + \n :global ValidateSyntax;\r\ + \n :global CommentsInScripts;\r\ + \n\r\ + \n :if ([ \$CertificateAvailable \"Root YE\" \"fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Downloading certificate failed, trying wit\ + hout.\");\r\ + \n }\r\ + \n\r\ + \n :foreach Script in=\$Scripts do={\r\ + \n :if ([ :len [ /system/script/find where name=\$Script ] ] = 0) do={\ + \r\ + \n \$LogPrint info \$0 (\"Adding new script: \" . \$Script);\r\ + \n /system/script/add name=\$Script owner=\$Script source=\"#!rsc by \ + Vados\\n\" comment=\$NewComment;\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :local CommitIdBefore \$CommitId;\r\ + \n :local GlobalConfVersionBefore \$GlobalConfVersion;\r\ + \n :local ReloadGlobal false;\r\ + \n :local DeviceMode [ /system/device-mode/get ];\r\ + \n\r\ + \n :if (\$CheckSumsVerify = true) do={\r\ + \n :local CheckSums ({});\r\ + \n :do {\r\ + \n :local Url (\$GlobalScriptsUrl . \$SumsDirAndFileName . \$GlobalScr\ + iptsUrlSuffix);\r\ + \n \$LogPrint debug \$0 (\"Fetching checksums from url: \" . \$Url);\ + \r\ + \n :set CheckSums [ :deserialize from=json ([ /tool/fetch check-certi\ + ficate=yes-without-crl \\\r\ + \n http-header-field=({ [ \$FetchUserAgentStr \$0 ] }) \$Url output\ + =user as-value ]->\"data\") ];\r\ + \n } on-error={ \$LogPrint warning \$0 (\"Err = \" . \$Err); }\r\ + \n }\r\ + \n\r\ + \n :foreach Script in=[ /system/script/find where source~\"^#!rsc by Vado\ + s\" ] do={\r\ + \n :local ScriptVal [ /system/script/get \$Script ];\r\ + \n :local ScriptInfo [ \$ParseKeyValueStore (\$ScriptVal->\"comment\") \ + ];\r\ + \n :local SourceNew;\r\ + \n\r\ + \n :foreach Scheduler in=[ /system/scheduler/find where on-event~(\"\\\ + \\b\" . \$ScriptVal->\"name\" . \"\\\\b\") ] do={\r\ + \n :local SchedulerVal [ /system/scheduler/get \$Scheduler ];\r\ + \n :if (\$ScriptVal->\"policy\" != \$SchedulerVal->\"policy\") do={\r\ + \n \$LogPrint warning \$0 (\"Policies differ for script '\" . \$Scr\ + iptVal->\"name\" . \"' and its scheduler '\" . \$SchedulerVal->\"name\" . \ + \"'!\");\r\ + \n }\r\ + \n }\r\ + \n :do {\r\ + \n :if (\$ScriptInfo->\"ignore\" = true) do={\r\ + \n \$LogPrint debug \$0 (\"Ignoring script '\" . \$ScriptVal->\"nam\ + e\" . \"', as requested.\");\r\ + \n :error true;\r\ + \n }\r\ + \n :if (\$CheckSumsVerify = true) do={\r\ + \n :local CheckSum (\$CheckSums->(\$ScriptVal->\"name\"));\r\ + \n :if ([ :len (\$ScriptInfo->\"base-url\") ] = 0 && [ :len (\$Scri\ + ptInfo->\"url-suffix\") ] = 0 && \\\r\ + \n [ :convert transform=md5 to=hex [ :tolf (\$ScriptVal->\"sou\ + rce\") ] ] = \$CheckSum) do={\r\ + \n \$LogPrint debug \$0 (\"Checksum for script '\" . \$ScriptVal-\ + >\"name\" . \"' matches, ignoring.\");\r\ + \n :error true;\r\ + \n }\r\ + \n }\r\ + \n :if ([ :len (\$ScriptInfo->\"certificate\") ] > 0) do={\r\ + \n :if ([ \$CertificateAvailable (\$ScriptInfo->\"certificate\") \"\ + fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Downloading certificate failed, tryi\ + ng without.\");\r\ + \n }\r\ + \n }\r\ + \n :onerror Err {\r\ + \n :local BaseUrl [ \$EitherOr (\$ScriptInfo->\"base-url\") \$Globa\ + lScriptsUrl ];\r\ + \n :local UrlSuffix [ \$EitherOr (\$ScriptInfo->\"url-suffix\") \$G\ + lobalScriptsUrlSuffix ];\r\ + \n :local Url (\$BaseUrl . \$ScriptVal->\"name\" . \".rsc\" . \$Url\ + Suffix);\r\ + \n \$LogPrint warning \$0 (\"Fetching script '\" . (\$ScriptVal->\"\ + name\") . \"' from url: \" . \$Url);\r\ + \n :local Result [ /tool/fetch check-certificate=yes-without-crl \\\ + \r\ + \n http-header-field=({ [ \$FetchUserAgentStr \$0 ] }) \$Url outp\ + ut=user as-value ];\r\ + \n :if (\$Result->\"status\" = \"finished\") do={\r\ + \n :set SourceNew [ :tolf (\$Result->\"data\") ];\r\ + \n } \r\ + \n } do={\r\ + \n \$LogPrint warning \$0 (\"Failed fetching script '\" . \$ScriptV\ + al->\"name\" . \"': \" . \$Err);\r\ + \n :if (\$Err != \"Fetch failed with status 404\") do={\r\ + \n :error false;\r\ + \n }\r\ + \n :if (\$ScriptVal->\"source\" = \"#!rsc by Vados\") do={\r\ + \n \$LogPrint warning \$0 (\"Removing dummy. Typo on installation\ + \?\");\r\ + \n /system/script/remove \$Script;\r\ + \n :error false;\r\ + \n }\r\ + \n :if (\$CheckSumsVerify = true) do={\r\ + \n :if ([ :len (\$ScriptInfo->\"base-url\") ] = 0 && [ :len (\$Sc\ + riptInfo->\"url-suffix\") ] = 0 && \\\r\ + \n [ :len \$CheckSum ] = 0) do={\r\ + \n \$LogPrint warning \$0 (\"Added the script manually\? Skip u\ + pdates with 'ignore=true' in comment.\");\r\ + \n }\r\ + \n }\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :if ([ :len \$SourceNew ] = 0) do={\r\ + \n \$LogPrint debug \$0 (\"No update for script '\" . (\$ScriptVal-\ + >\"name\") . \"'.\");\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :local SourceCRLF [ :tocrlf \$SourceNew ];\r\ + \n :if (\$InitSetup != true && ((\$ScriptVal->\"name\") != \"AM-Globa\ + lConfig\" || (\$ScriptVal->\"name\") != \"AM-GlobalFunc\")) do={\r\ + \n :if (\$SourceNew = (\$ScriptVal->\"source\") || \$SourceCRLF = (\ + \$ScriptVal->\"source\")) do={\r\ + \n \$LogPrint debug \$0 (\"Script '\" . (\$ScriptVal->\"name\") \ + . \"' source did not change. End of lines set to CRLF.\");\r\ + \n if (\$ScriptUpdatesCRLF = false) do={\r\ + \n :put [ :tolf \$SourceNew ];\r\ + \n \$LogPrint debug \$0 (\"Script '\" . (\$ScriptVal->\"name\"\ + ) . \"' source update end of lines to LF.\");\r\ + \n }\r\ + \n :error false;\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if ([ :pick \$SourceNew 0 14 ] != \"#!rsc by Vados\") do={\r\ + \n \$LogPrint warning \$0 (\"Looks like new script '\" . \$ScriptVa\ + l->\"name\" . \"' is not valid (missing shebang = '\$shebang') Ignoring!\"\ + );\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :if (\$CommentsInScripts = true) do={\r\ + \n :local CommentLine [ \$Grep \$SourceNew (\"\\23 Script comment:\ + \_\") ];\r\ + \n :if ([ :len \$CommentLine ] = 0) do={\r\ + \n \$LogPrint debug \$0 (\"The search in new script '\" . (\$Scr\ + iptVal->\"name\") . \"' for comment in script body, is not found result Ig\ + noring!\");\r\ + \n :error false;\r\ + \n } else={\r\ + \n :if ([ :len (\$ScriptVal->\"comment\")] = 0 || [ :len (\$Scri\ + ptVal->\"comment\")] != [ :pick \$CommentLine ([ :find \$CommentLine \":\"\ + ] + 2) [ :len \$CommentLine] ]) do={\r\ + \n \$LogPrint debug \$0 (\"Exsiting comment for script '\" . (\ + \$ScriptVal->\"name\") . \"' is '\" . (\$ScriptVal->\"comment\") . \"'.\")\ + ;\r\ + \n :set (\$ScriptVal->\"comment\") [ :pick \$CommentLine ([ :f\ + ind \$CommentLine \":\"] + 2) [ :len \$CommentLine] ];\r\ + \n \$LogPrint debug \$0 (\"New comment (\" . (\$ScriptVal->\"c\ + omment\") . \") for script '\" . (\$ScriptVal->\"name\") . \"' has been ad\ + ded!\");\r\ + \n } else={\r\ + \n \$LogPrint debug \$0 (\"Comment for script '\" . (\$ScriptV\ + al->\"name\") . \"' did not change.\");\r\ + \n :error false;\r\ + \n }\r\ + \n } \r\ + \n }\r\ + \n\r\ + \n :local RequiredROS ([ \$ParseKeyValueStore [ \$Grep \$SourceNew (\ + \"\\23 requires RouterOS, \") ] ]->version);\r\ + \n :if ([ \$RequiredRouterOS \$0 [ \$EitherOr \$RequiredROS \"0.0\" ]\ + \_false ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"The script '\" . (\$ScriptVal->\"name\ + \") . \"' requires RouterOS \" . \\\r\ + \n \$RequiredROS . \", which is not met by your installation. I\ + gnoring!\");\r\ + \n :error false;\r\ + \n }\r\ + \n :local RequiredDM [ \$ParseKeyValueStore [ \$Grep \$SourceNew (\"\ + \\23 requires device-mode, \") ] ];\r\ + \n :local MissingDM ({});\r\ + \n :foreach Feature,Value in=\$RequiredDM do={\r\ + \n :if ([ :typeof (\$DeviceMode->\$Feature) ] = \"bool\" && (\$Devi\ + ceMode->\$Feature) = false) do={\r\ + \n :set MissingDM (\$MissingDM, \$Feature);\r\ + \n }\r\ + \n }\r\ + \n :if ([ :len \$MissingDM ] > 0) do={\r\ + \n \$LogPrint warning \$0 (\"The script '\" . (\$ScriptVal->\"name\ + \") . \"' requires disabled \" . \\\r\ + \n \"device-mode features (\" . [ :tostr \$MissingDM ] . \"). I\ + gnoring!\");\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :if ([ \$ValidateSyntax \$SourceNew ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Syntax validation for script '\" . (\$\ + ScriptVal->\"name\") . \"' failed! Ignoring!\");\r\ + \n :error false;\r\ + \n }\r\ + \n \r\ + \n :log info (\"Updating script: \" . (\$ScriptVal->\"name\") . \" Sc\ + ript comment: \" . (\$ScriptVal->\"comment\"));\r\ + \n\r\ + \n /system/script/set owner=(\$ScriptVal->\"name\") comment=(\$Script\ + Val->\"comment\") \\\r\ + \n source=[ \$IfThenElse (\$ScriptUpdatesCRLF = true) \$SourceCRL\ + F \$SourceNew ] \$Script;\r\ + \n :if (\$ScriptVal->\"name\" = \"AM-GlobalConfig\" || \\\r\ + \n \$ScriptVal->\"name\" = \"AM-GlobalFunc\" || \\\r\ + \n \$ScriptVal->\"name\" ~ (\"^(AM-GlobalFunc\\\\.d|mod)/.\")) d\ + o={\r\ + \n :set ReloadGlobal true;\r\ + \n }\r\ + \n } on-error={ }\r\ + \n }\r\ + \n :if (\$ReloadGlobal = true) do={\r\ + \n \$LogPrint info \$0 (\"Reloading global configuration and functions.\ + \");\r\ + \n :onerror Err {\r\ + \n :global InitSetup (a);\r\ + \n /system/script {run AM-GlobalConfig; run AM-GlobalFunc;};\r\ + \n } do={\$LogPrint error \$0 (\"Reloading global configuration and fun\ + ctions failed! \" . \$Err)}\r\ + \n }\r\ + \n :if (\$GlobalConfVersionBefore > \$GlobalConfVersion) do={\r\ + \n \$LogPrint warning \$0 (\"The configuration version decreased from \ + \" . \\\r\ + \n \$GlobalConfVersionBefore . \" to \" . \$GlobalConfVersion . \\\r\ + \n \". Installed an older version\?\");\r\ + \n }\r\ + \n :if (\$GlobalConfVersionBefore < \$GlobalConfVersion) do={\r\ + \n :global GlobalConfChanges;\r\ + \n :global GlobalConfMigration;\r\ + \n :local ChangeLogCode;\r\ + \n :if ([ :len \$ChangeLogCode ] > 0) do={\r\ + \n :if ([ \$ValidateSyntax \$ChangeLogCode ] = true) do={\r\ + \n :onerror Err {\r\ + \n [ :parse \$ChangeLogCode ];\r\ + \n } do={\$LogPrint warning \$0 (\"The changelog failed to run: \" \ + . \$Err)}\r\ + \n } else={\$LogPrint warning \$0 (\"The changelog failed syntax vali\ + dation!\")}\r\ + \n }\r\ + \n :if ([ :len \$GlobalConfMigration ] > 0) do={\r\ + \n :for I from=(\$GlobalConfVersionBefore + 1) to=\$GlobalConfVersion\ + \_do={\r\ + \n :local Migration (\$GlobalConfMigration->[ :tostr \$I ]);\r\ + \n :do {\r\ + \n :if ([ :typeof \$Migration ] != \"str\") do={\r\ + \n \$LogPrint debug \$0 (\"Migration code for change \" . \$I .\ + \_\" is not available.\");\r\ + \n :error false;\r\ + \n }\r\ + \n :if ([ \$ValidateSyntax \$Migration ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Migration code for change \" . \$I\ + \_. \" failed syntax validation!\");\r\ + \n :error false}\r\ + \n \$LogPrint info \$0 (\"Applying migration for change \" . \$I \ + . \": \" . \$Migration);\r\ + \n :onerror Err {\r\ + \n [ :parse \$Migration ];\r\ + \n } do={\$LogPrint warning \$0 (\"Migration code for change \" .\ + \_\$I . \" failed to run: \" . \$Err)}\r\ + \n } on-error={ }\r\ + \n }\r\ + \n }\r\ + \n :local NotificationMessage (\"The configuration version on \" . \$Id\ + entity . \" increased \" . \\\r\ + \n \"to \" . \$GlobalConfVersion . \", current configuration may nee\ + d modification. \" . \\\r\ + \n \"Please review and update global-config-overlay, then re-run glo\ + bal-config.\");\r\ + \n \$LogPrint info \$0 (\$NotificationMessage);\r\ + \n :if ([ :len \$GlobalConfChanges ] > 0) do={\r\ + \n :set NotificationMessage (\$NotificationMessage . \"\\n\\nChanges:\ + \");\r\ + \n :for I from=(\$GlobalConfVersionBefore + 1) to=\$GlobalConfVersion\ + \_do={\r\ + \n :local Change (\$GlobalConfChanges->[ :tostr \$I ]);\r\ + \n :set NotificationMessage (\$NotificationMessage . \"\\n \" . \\\ + \r\ + \n [ \$SymbolForNotification \"pushpin\" \"*\" ] . \$Change);\r\ + \n \$LogPrint info \$0 (\"Change \" . \$I . \": \" . \$Change);\r\ + \n }\r\ + \n } else={\r\ + \n :set NotificationMessage (\$NotificationMessage . \"\\n\\nNews and\ + \_changes are not available.\");\r\ + \n\r\ + \n }\r\ + \n\r\ + \n :if ([ :len \$GlobalConfChanges ] > 0) do={ :set GlobalConfChanges; \ + } else={ :global GlobalConfChanges (a); }\r\ + \n :if ([ :len \$GlobalConfMigration ] > 0) do={ :set GlobalConfMigrati\ + on; } else={ :global GlobalConfMigration (a); }\r\ + \n }\r\ + \n} do={\r\ + \n :global ExitOnError; \$ExitOnError \$0 \$Err;\r\ + \n} }\r\ + \n\r\ + \n# load modules\r\ + \n:foreach Script in=[ /system/script/find where name ~ \"^(global-functio\ + ns\\\\.d|mod)/.\" ] do={\r\ + \n :local ScriptVal [ /system/script/get \$Script ];\r\ + \n :if ([ \$ValidateSyntax (\$ScriptVal->\"source\") ] = true) do={\r\ + \n :onerror Err {\r\ + \n /system/script/run \$Script;\r\ + \n } do={\r\ + \n \$LogPrint error \$0 (\"Module '\" . \$ScriptVal->\"name\" . \"' f\ + ailed to run: \" . \$Err);\r\ + \n }\r\ + \n } else={\r\ + \n \$LogPrint error \$0 (\"Module '\" . \$ScriptVal->\"name\" . \"' fai\ + led syntax validation, skipping.\");\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n# Add and fix global scheduler\r\ + \n:put \"Add or/and fix Global scheduler...\";\r\ + \n:local OnEvent \"/system/script { run AM-GlobalConfig; run AM-GlobalFunc\ + ; }\";\r\ + \n:local Comment \"Every Day Set Global Environment Variables\";\r\ + \n:if ([ :len [ /system/scheduler/find where name=\"AM-SetGlobalEnv\" ] ] \ + = 0) do={\r\ + \n /system/scheduler/add name=\"AM-SetGlobalEnv\" comment=\$Comment on-ev\ + ent=\$OnEvent start-time=startup;\r\ + \n}\r\ + \n:if ([ /system/scheduler/get \"AM-SetGlobalEnv\" comment ] != \$Comment)\ + \_do={\r\ + \n /system/scheduler/set \"AM-SetGlobalEnv\" comment=\$Comment;\r\ + \n}\r\ + \n:if ([ /system/scheduler/get \"AM-SetGlobalEnv\" on-event ] != \$OnEvent\ + ) do={\r\ + \n /system/scheduler/set \"AM-SetGlobalEnv\" on-event=\$OnEvent;\r\ + \n}\r\ + \n# Add Update Script scheduler\r\ + \n:put \"Add or/and fix Update Script scheduler\";\r\ + \n:set OnEvent \":global ScriptInstallUpdate; \\\$ScriptInstallUpdate;\";\ + \r\ + \n:set Comment \"Every Day Check For Update Scripts\";\r\ + \n:if ([ :len [ /system/scheduler/find where name=\"AM-ScriptInstallUpdate\ + \" ] ] = 0) do={\r\ + \n/system/scheduler/add name=\"AM-ScriptInstallUpdate\" comment=\$Comment \ + start-time=startup interval=1d on-event=\$OnEvent;\r\ + \n}\r\ + \n\r\ + \n:onerror Err {\r\ + \n :global GlobalConfReady;\r\ + \n :retry { :if (\$GlobalConfReady != true) do={ :error (\"Global configs\ + \_not ready.\"); }; } delay=100ms max=5;\r\ + \n\r\ + \n:log warning (\"This script \$ScriptName Loaded \" . \\\r\ + \n [ \$IfThenElse (\$CommitId != \"unknown\") ([ \$CommitBrief ] . \" \ + \") ] . \\\r\ + \n \"on \" . \$devBoardName . \"\\nRouterOS: \" . \$devCurVersion . \"\ + \\nArchitecture: \" . \$devCurArch . \"\\nIdentity: \" . \$Identity);\r\ + \n # signal we are ready\r\ + \n:set GlobalFuncReady true;\r\ + \n:set ExitOK true;\r\ + \n\r\ + \n:log info (\$0 . \"Global configs ready!\");\r\ + \n} do={\r\ + \n :global ExitOnError; \$ExitOnError \$0 \$Err;\r\ + \n}\r\ + \n" +add dont-require-permissions=no name=fetchCustom owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + \n#/tool/fetch mode=https url=(\$GlobalScriptsUrl . \".bak/mikro_bak_rsa@1\ + 0.30.30.10.pub\") dst-path=\"/mikro_bak_rsa@10.30.30.10.pub\";\ + \n#\ + \n#root.mnuc.backuper\ + \n\ + \n#/tool/fetch mode=https url=(\$GlobalScriptsUrl . \".bak/mikro_bak_rsa@1\ + 0.30.30.81.pub\") dst-path=\"/mikro_bak_rsa@10.30.30.81.pub\";\ + \n#:local Script AM-GlobalConfig;\ + \n#/system/script/add name=\$Script owner=\$Script source=([ /tool/fetch c\ + heck-certificate=yes-without-crl url=(\$GlobalScriptsUrl . \$Script . \".r\ + sc\") output=user as-value ]->\"data\");\ + \n#/usb1/mikro_bak_rsa.pub\ + \n#:local FetchFile AM-DOHCert.rsc\ + \n\ + \n:global GlobalScriptsUrl \"https://ros.vados.ru/tmp/\";\ + \n:global MkDir;\ + \n\ + \n:local FetchFile Manager@id_rsa.pub\ + \n:local localDir \"/\";\ + \n\ + \n\$MkDir \$localDir;\ + \n\ + \n/tool/fetch mode=https url=(\$GlobalScriptsUrl . \$FetchFile) dst-path=(\ + \$localDir . \$FetchFile);\ + \n" +add dont-require-permissions=yes name=MkTmpfs owner=vados policy=\ + ftp,read,write,policy,test,sniff,sensitive source=\ + ":global MkDir;\r\ + \n\r\ + \n\$MkDir /usb1/tmpfs;" +add dont-require-permissions=no name=CreateContainer owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":\ + local contIF \"veth1\";\r\ + \n:local contIP \"10.10.10.10/24\";\r\ + \n:local contGW \"10.10.10.1\";\r\ + \n#/interface/veth/add name=\$contIF address=\$contIP gateway=\$contGW;\r\ + \n:local contBridgeIF \"Bridge-DOC\";\r\ + \n:local contBridgeIP \"10.10.10.1/24\";\r\ + \n/interface/bridge/add name=\$contBridgeIF;\r\ + \n/ip/address/add address=\$contBridgeIP interface=\$contBridgeIF;\r\ + \n/interface/bridge/port/add bridge=\$contBridgeIF interface=\$contIF;\r\ + \n/container/config/set ram-high=768M registry-url=https://registry-1.dock\ + er.io tmpdir=sata1/tmp\r\ + \n" +add dont-require-permissions=no name=carkeeperbot owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/\ + container/mounts/add list=carkeeper_configs/.profile src=sata1/carkeeper/c\ + onfigs/.profile dst=/root/.ashrc;\r\ + \n/container/mounts/add list=carkeeper_configs/php.ini src=sata1/carkeeper\ + /configs/php.ini dst=/etc/php82/php.ini;\r\ + \n#/container/mounts/add list=carkeeper_configs src=sata1/carkeeper/config\ + s dst=/configs;\r\ + \n/container/mounts/add list=carkeeper_app src=sata1/carkeeper/app dst=/ap\ + p;\r\ + \n/container/mounts/add list=carkeeper_scripts src=sata1/carkeeper/scripts\ + \_dst=/scripts;\r\ + \n/container/mounts/add list=carkeeper_configs/sshd_config src=sata1/carke\ + eper/configs/sshd_config dst=/etc/ssh/sshd_config;\r\ + \n\r\ + \n/container/mounts/add list=carkeeper_tmp src=sata1/carkeeper/tmp dst=/va\ + r/tmp;\r\ + \n/container/mounts/add list=carkeeper_sync src=sata1/carkeeper/sync dst=/\ + var/sync;\r\ + \n" +add comment="Enable or disable ip addresses based on bridge port state" \ + dont-require-permissions=no name=mod/AM-IPbridge owner=mod/AM-IPbridge \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source="#!rsc by Vados\r\ + \n# RouterOS script: mod/AM-IPbridge\r\ + \n# Script comment: Enable or disable ip addresses based on bridge port st\ + ate\r\ + \n# Copyright (c) 2005-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# requires device-mode, fetch\r\ + \n#\r\ + \n:local ScriptName [ :jobname ];\r\ + \n\r\ + \n# Add this Script scheduler\r\ + \n:if ([ :len [ /system/scheduler/find where name=\$ScriptName ] ] = 0) do\ + ={\r\ + \n:log warning (\"SystemScheduler for script: \" . \$ScriptName . \" is NO\ + T SET! Fix It Now!\");\r\ + \n:local OnEvent \"/system/script/run \$ScriptName;\";\r\ + \n:local Comment \"Every Day Check For Fake Bridges\";\r\ + \n/system/scheduler/add name=\$ScriptName comment=\$Comment start-time=sta\ + rtup interval=1d on-event=\$OnEvent;\r\ + \n}\r\ + \n\r\ + \n:foreach Bridge in=[ /interface/bridge/find ] do={\r\ + \n :local BrName [ /interface/bridge/get \$Bridge name ];\r\ + \n :if ([ :len [ /interface/bridge/port/find where bridge=\$BrName ] ] > \ + 0) do={\r\ + \n :if ([ :len [ /interface/bridge/port/find where bridge=\$BrName and \ + inactive=no ] ] = 0) do={\r\ + \n /ip/address/disable [ find where !dynamic interface=\$BrName ];\r\ + \n } else={\r\ + \n /ip/address/enable [ find where !dynamic interface=\$BrName ];\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n" +add comment="Use Telegram to chat with your Router and send commands" \ + dont-require-permissions=no name=mod/AM-TG-Bot owner=mod/AM-TG-Bot \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source="#!rsc by Vados\r\ + \n# RouterOS script: mod/AM-TG-Bot\r\ + \n# Script comment: Use Telegram to chat with your Router and send command\ + s\r\ + \n# Copyright (c) 2007-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# requires device-mode, fetch\r\ + \n:local ExitOK false;\r\ + \n:onerror Err {\r\ + \n :global GlobalConfReady; :global GlobalFuncReady;\r\ + \n :retry { :if (\$GlobalConfReady != true || \$GlobalFuncReady != true) \ + \\\r\ + \n do={ :error (\"Global config and/or functions not ready.\"); }; } \ + delay=500ms max=50;\r\ + \n :local ScriptName [ :jobname ];\r\ + \n\r\ + \n :global Identity;\r\ + \n :global TelegramChatActive;\r\ + \n :global TelegramChatGroups;\r\ + \n :global TelegramChatId;\r\ + \n :global TelegramChatIdsTrusted;\r\ + \n :global TelegramChatOffset;\r\ + \n :global TelegramChatRunTime;\r\ + \n :global TelegramMessageIDs;\r\ + \n :global TelegramRandomDelay;\r\ + \n :global TelegramTokenId;\r\ + \n\r\ + \n :global CertificateAvailable;\r\ + \n :global EitherOr;\r\ + \n :global EscapeForRegEx;\r\ + \n :global FileExists;\r\ + \n :global GetRandom20CharAlNum;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global LogPrintVerbose;\r\ + \n :global MAX;\r\ + \n :global MIN;\r\ + \n :global MkDir;\r\ + \n :global RandomDelay;\r\ + \n :global RmDir;\r\ + \n :global ScriptLock;\r\ + \n :global SendTelegram2;\r\ + \n :global SymbolForNotification;\r\ + \n :global ValidateSyntax;\r\ + \n :global WaitForFile;\r\ + \n :global WaitFullyConnected;\r\ + \n\r\ + \n :if ([ \$ScriptLock \$ScriptName ] = false) do={\r\ + \n :set ExitOK true;\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n \$WaitFullyConnected;\r\ + \n\r\ + \n :if ([ :len [ /system/scheduler/find where name=\$ScriptName ] ] = 0) \ + do={\r\ + \n /system/scheduler/add start-time=startup interval=30s name=\$ScriptN\ + ame on-event=\"/system/script/run \$ScriptName;\" comment=\"Telegram Chat \ + Bot\";\r\ + \n }\r\ + \n :if ([ :typeof \$TelegramChatOffset ] != \"array\") do={\r\ + \n :set TelegramChatOffset { 0; 0; 0 };\r\ + \n }\r\ + \n :if ([ :typeof \$TelegramRandomDelay ] != \"num\") do={\r\ + \n :set TelegramRandomDelay 0;\r\ + \n }\r\ + \n\r\ + \n :if ([ \$CertificateAvailable \"Go Daddy Root Certificate Authority - \ + G2\" \"fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$ScriptName (\"Downloading required certificate \ + failed.\");\r\ + \n :set ExitOK true;\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n \$RandomDelay \$TelegramRandomDelay;\r\ + \n\r\ + \n :local Data false;\r\ + \n :for I from=1 to=4 do={\r\ + \n :if (\$Data = false) do={\r\ + \n :onerror Err {\r\ + \n :set Data ([ /tool/fetch check-certificate=yes-without-crl outpu\ + t=user \\\r\ + \n (\"https://api.telegram.org/bot\" . \$TelegramTokenId . \"/get\ + Updates\?offset=\" . \\\r\ + \n \$TelegramChatOffset->0 . \"&allowed_updates=%5B%22message%22%\ + 5D\") as-value ]->\"data\");\r\ + \n :set TelegramRandomDelay [ \$MAX 0 (\$TelegramRandomDelay - 1) ]\ + ;\r\ + \n } do={\r\ + \n :if (\$I < 4) do={\r\ + \n \$LogPrint debug \$ScriptName (\"Fetch failed, \" . \$I . \". \ + try: \" . \$Err);\r\ + \n :set TelegramRandomDelay [ \$MIN 15 (\$TelegramRandomDelay + 5\ + ) ];\r\ + \n :delay ((\$I * \$I) . \"s\");\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if (\$Data = false) do={\r\ + \n \$LogPrint warning \$ScriptName (\"Failed getting updates.\");\r\ + \n :set ExitOK true;\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :local JSON [ :deserialize from=json value=\$Data ];\r\ + \n :local UpdateID 0;\r\ + \n :local Uptime [ /system/resource/get uptime ];\r\ + \n :foreach Update in=(\$JSON->\"result\") do={\r\ + \n :set UpdateID (\$Update->\"update_id\");\r\ + \n \$LogPrintVerbose debug \$ScriptName (\"Update \" . \$UpdateID . \":\ + \_\" . [ :serialize to=json \$Update ]);\r\ + \n\r\ + \n :local Message (\$Update->\"message\");\r\ + \n :local IsAnyReply ([ :typeof (\$Message->\"reply_to_message\") ] = \ + \"array\");\r\ + \n :local IsMyReply (\$TelegramMessageIDs->[ :tostr (\$Message->\"reply\ + _to_message\"->\"message_id\") ]);\r\ + \n :if ((\$IsMyReply = 1 || \$TelegramChatOffset->0 > 0 || \$Uptime > 5\ + m) && \$UpdateID >= \$TelegramChatOffset->2) do={\r\ + \n :local Trusted false;\r\ + \n :local Chat (\$Message->\"chat\");\r\ + \n :local From (\$Message->\"from\");\r\ + \n :local Command (\$Message->\"text\");\r\ + \n :local ThreadId [ \$IfThenElse (\$Message->\"is_topic_message\") (\ + \$Message->\"message_thread_id\") \"\" ];\r\ + \n\r\ + \n :foreach IdsTrusted in=(\$TelegramChatId, \$TelegramChatIdsTrusted\ + ) do={\r\ + \n :if (\$From->\"id\" = \$IdsTrusted || \\\r\ + \n \$From->\"username\" = \$IdsTrusted || \\\r\ + \n \$Chat->\"id\" = \$IdsTrusted) do={\r\ + \n :set Trusted true;\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if (\$Trusted = true) do={\r\ + \n :local Done false;\r\ + \n :if (\$Command = \"\?\") do={\r\ + \n \$LogPrint info \$ScriptName (\"Sending notice for update \" .\ + \_\$UpdateID . \".\");\r\ + \n \$SendTelegram2 ({ origin=\$ScriptName; chatid=(\$Chat->\"id\"\ + ); silent=true; \\\r\ + \n replyto=(\$Message->\"message_id\"); threadid=\$ThreadId; \\\ + \r\ + \n subject=([ \$SymbolForNotification \"speech-balloon\" ] . \"\ + Telegram Chat\"); \\\r\ + \n message=([ \$IfThenElse ([ :len (\$From->\"first_name\") ] >\ + \_0) (\"Hello \" . (\$From->\"first_name\") . \"!\\n\\n\") ] . \\\r\ + \n \"Online\" . [ \$IfThenElse \$TelegramChatActive \" (and a\ + ctive!)\" ] . \", awaiting your commands!\") });\r\ + \n :set Done true;\r\ + \n }\r\ + \n :if (\$Done = false && [ :pick \$Command 0 1 ] = \"!\") do={\r\ + \n :if (\$Command ~ (\"^! *(\" . [ \$EscapeForRegEx \$Identity ] \ + . \"|@\" . \$TelegramChatGroups . \")\\\$\")) do={\r\ + \n :set TelegramChatActive true;\r\ + \n } else={\r\ + \n :set TelegramChatActive false;\r\ + \n }\r\ + \n \$LogPrint info \$ScriptName (\"Now \" . [ \$IfThenElse \$Tele\ + gramChatActive \"active\" \"passive\" ] . \\\r\ + \n \" from update \" . \$UpdateID . \"!\");\r\ + \n :set Done true;\r\ + \n }\r\ + \n :if (\$Done = false && (\$IsMyReply = 1 || (\$IsAnyReply = false\ + \_&& \\\r\ + \n \$TelegramChatActive = true)) && [ :len \$Command ] > 0) do\ + ={\r\ + \n :if ([ \$ValidateSyntax \$Command ] = true) do={\r\ + \n :local State \"\";\r\ + \n :local File (\"tmpfs/telegram-chat/\" . [ \$GetRandom20CharA\ + lNum 6 ]);\r\ + \n :if ([ \$MkDir \"tmpfs/telegram-chat\" ] = false) do={\r\ + \n \$LogPrint error \$ScriptName (\"Failed creating directory\ + !\");\r\ + \n :set ExitOK true;\r\ + \n :error false;\r\ + \n }\r\ + \n \$LogPrint info \$ScriptName (\"Running command from update \ + \" . \$UpdateID . \": \" . \$Command);\r\ + \n :execute script=(\":do {\\n\" . \$Command . \"\\n} on-error=\ + { /file/add name=\\\"\" . \$File . \".failed\\\" };\" . \\\r\ + \n \"/file/add name=\\\"\" . \$File . \".done\\\"\") file=(\$\ + File . \"\\00\");\r\ + \n :if ([ \$WaitForFile (\$File . \".done\") [ \$EitherOr \$Tel\ + egramChatRunTime 20s ] ] = false) do={\r\ + \n :set State ([ \$SymbolForNotification \"warning-sign\" ] .\ + \_\"The command did not finish, still running in background.\\n\\n\");\r\ + \n }\r\ + \n :if ([ \$FileExists (\$File . \".failed\") ] = true) do={\r\ + \n :set State ([ \$SymbolForNotification \"cross-mark\" ] . \ + \"The command failed with an error!\\n\\n\");\r\ + \n }\r\ + \n :local Content ([ /file/read chunk-size=32768 file=\$File as\ + -value ]->\"data\");\r\ + \n \$SendTelegram2 ({ origin=\$ScriptName; chatid=(\$Chat->\"id\ + \"); silent=true; \\\r\ + \n replyto=(\$Message->\"message_id\"); threadid=\$ThreadId; \ + \\\r\ + \n subject=([ \$SymbolForNotification \"speech-balloon\" ] . \ + \"Telegram Chat\"); \\\r\ + \n message=([ \$SymbolForNotification \"gear\" ] . \"Command:\ + \\n\" . \$Command . \"\\n\\n\" . \\\r\ + \n \$State . [ \$IfThenElse ([ :len \$Content ] > 0) \\\r\ + \n ([ \$SymbolForNotification \"memo\" ] . \"Output:\\n\" .\ + \_\$Content) \\\r\ + \n ([ \$SymbolForNotification \"memo\" ] . \"No output.\") \ + ]) });\r\ + \n \$RmDir \"tmpfs/telegram-chat\";\r\ + \n } else={\r\ + \n \$LogPrint info \$ScriptName (\"The command from update \" .\ + \_\$UpdateID . \" failed syntax validation!\");\r\ + \n \$SendTelegram2 ({ origin=\$ScriptName; chatid=(\$Chat->\"id\ + \"); silent=false; \\\r\ + \n replyto=(\$Message->\"message_id\"); threadid=\$ThreadId; \ + \\\r\ + \n subject=([ \$SymbolForNotification \"speech-balloon\" ] . \ + \"Telegram Chat\"); \\\r\ + \n message=([ \$SymbolForNotification \"gear\" ] . \"Command:\ + \\n\" . \$Command . \"\\n\\n\" . \\\r\ + \n [ \$SymbolForNotification \"cross-mark\" ] . \"The comma\ + nd failed syntax validation!\") });\r\ + \n }\r\ + \n }\r\ + \n } else={\r\ + \n :local MessageText (\"Received a message from untrusted contact \ + \" . \\\r\ + \n [ \$IfThenElse ([ :len (\$From->\"username\") ] = 0) \"without\ + \_username\" (\"'\" . \$From->\"username\" . \"'\") ] . \\\r\ + \n \" (ID \" . \$From->\"id\" . \") in update \" . \$UpdateID . \ + \"!\");\r\ + \n :if (\$Command ~ (\"^! *\" . [ \$EscapeForRegEx \$Identity ] . \ + \"\\\$\")) do={\r\ + \n \$LogPrint warning \$ScriptName \$MessageText;\r\ + \n \$SendTelegram2 ({ origin=\$ScriptName; chatid=(\$Chat->\"id\"\ + ); silent=false; \\\r\ + \n replyto=(\$Message->\"message_id\"); threadid=\$ThreadId; \\\ + \r\ + \n subject=([ \$SymbolForNotification \"speech-balloon\" ] . \"\ + Telegram Chat\"); \\\r\ + \n message=(\"You are not trusted.\") });\r\ + \n } else={\r\ + \n \$LogPrint info \$ScriptName \$MessageText;\r\ + \n }\r\ + \n }\r\ + \n } else={\r\ + \n \$LogPrint debug \$ScriptName (\"Already handled update \" . \$Upd\ + ateID . \".\");\r\ + \n }\r\ + \n }\r\ + \n :set TelegramChatOffset ([ :pick \$TelegramChatOffset 1 3 ], \\\r\ + \n [ \$IfThenElse (\$UpdateID >= \$TelegramChatOffset->2) (\$UpdateID +\ + \_1) (\$TelegramChatOffset->2) ]);\r\ + \n} do={\r\ + \n :global ExitError; \$ExitError \$ExitOK [ :jobname ] \$Err;\r\ + \n}\r\ + \n" +add comment="Script send Notifications in Telegram" dont-require-permissions=\ + no name=mod/AM-TG-Notifications owner=mod/AM-TG-Notifications policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: mod/AM-TG-Notifications\r\ + \n# Script comment: Script send Notifications in Telegram\r\ + \n# Copyright (c) 2007-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# requires device-mode, fetch, scheduler\r\ + \n:global FlushTelegramQueue;\r\ + \n:global GetTelegramChatId;\r\ + \n:global NotificationFunctions;\r\ + \n:global PurgeTelegramQueue;\r\ + \n:global SendTelegram;\r\ + \n:global SendTelegram2;\r\ + \n# flush telegram queue\r\ + \n:set FlushTelegramQueue do={ :onerror Err {\r\ + \n :global TelegramQueue;\r\ + \n :global TelegramMessageIDs;\r\ + \n :global CertificateAvailable;\r\ + \n :global IsFullyConnected;\r\ + \n :global LogPrint;\r\ + \n :if ([ \$IsFullyConnected ] = false) do={\r\ + \n \$LogPrint debug \$0 (\"System is not fully connected, not flushing.\ + \");\r\ + \n :return false;\r\ + \n }\r\ + \n :if ([ \$CertificateAvailable \"Go Daddy Root Certificate Authority - \ + G2\" \"fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Downloading required certificate failed.\"\ + );\r\ + \n :return false;\r\ + \n }\r\ + \n :local AllDone true;\r\ + \n :local QueueLen [ :len \$TelegramQueue ];\r\ + \n :if ([ :len [ /system/scheduler/find where name=\"_FlushTelegramQueue\ + \" ] ] > 0 && \$QueueLen = 0) do={\r\ + \n \$LogPrint warning \$0 (\"Flushing Telegram messages from scheduler,\ + \_but queue is empty.\");\r\ + \n }\r\ + \n :foreach Id,Message in=\$TelegramQueue do={\r\ + \n :if ([ :typeof \$Message ] = \"array\" ) do={\r\ + \n :onerror Err {\r\ + \n :local Data ([ /tool/fetch check-certificate=yes-without-crl out\ + put=user http-method=post \\\r\ + \n (\"https://api.telegram.org/bot\" . (\$Message->\"tokenid\") .\ + \_\"/sendMessage\") \\\r\ + \n http-data=(\$Message->\"http-data\") as-value ]->\"data\");\r\ + \n :set (\$TelegramQueue->\$Id);\r\ + \n :set (\$TelegramMessageIDs->[ :tostr ([ :deserialize from=json v\ + alue=\$Data ]->\"result\"->\"message_id\") ]) 1;\r\ + \n } do={\r\ + \n \$LogPrint debug \$0 (\"Sending queued Telegram message failed: \ + \" . \$Err);\r\ + \n :set AllDone false;\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n :if (\$AllDone = true && \$QueueLen = [ :len \$TelegramQueue ]) do={\r\ + \n /system/scheduler/remove [ find where name=\"_FlushTelegramQueue\" ]\ + ;\r\ + \n :set TelegramQueue;\r\ + \n }\r\ + \n} do={\r\ + \n :global ExitOnError; \$ExitOnError \$0 \$Err;\r\ + \n} }\r\ + \n# get the chat id\r\ + \n:set GetTelegramChatId do={ :onerror Err {\r\ + \n :global TelegramTokenId;\r\ + \n :global CertificateAvailable;\r\ + \n :global LogPrint;\r\ + \n :if ([ \$CertificateAvailable \"Go Daddy Root Certificate Authority - \ + G2\" \"fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Downloading required certificate failed.\"\ + );\r\ + \n :return false;\r\ + \n }\r\ + \n :local Data;\r\ + \n :onerror Err {\r\ + \n :set Data ([ /tool/fetch check-certificate=yes-without-crl output=us\ + er \\\r\ + \n (\"https://api.telegram.org/bot\" . \$TelegramTokenId . \"/getUpd\ + ates\?offset=0\" . \\\r\ + \n \"&allowed_updates=%5B%22message%22%5D\") as-value ]->\"data\");\ + \r\ + \n } do={\r\ + \n \$LogPrint warning \$0 (\"Fetching data failed: \" . \$Err);\r\ + \n :return false;\r\ + \n }\r\ + \n :local JSON [ :deserialize from=json value=\$Data ];\r\ + \n :local Count [ :len (\$JSON->\"result\") ];\r\ + \n :if (\$Count = 0) do={\r\ + \n \$LogPrint info \$0 (\"No message received.\");\r\ + \n :return false;\r\ + \n }\r\ + \n :local Message (\$JSON->\"result\"->(\$Count - 1)->\"message\");\r\ + \n \$LogPrint info \$0 (\"The chat id is: \" . (\$Message->\"chat\"->\"id\ + \"));\r\ + \n :if ((\$Message->\"is_topic_message\") = true) do={\r\ + \n \$LogPrint info \$0 (\"The thread id is: \" . (\$Message->\"message_\ + thread_id\"));\r\ + \n }\r\ + \n} do={\r\ + \n :global ExitOnError; \$ExitOnError \$0 \$Err;\r\ + \n} }\r\ + \n# send notification via telegram - expects one array argument\r\ + \n:set (\$NotificationFunctions->\"telegram\") do={\r\ + \n :local Notification \$1;\r\ + \n :global Identity;\r\ + \n :global IdentityExtra;\r\ + \n :global TelegramChatId;\r\ + \n :global TelegramChatIdOverride;\r\ + \n :global TelegramMessageIDs;\r\ + \n :global TelegramQueue;\r\ + \n :global TelegramThreadId;\r\ + \n :global TelegramThreadIdOverride;\r\ + \n :global TelegramTokenId;\r\ + \n :global TelegramTokenIdOverride;\r\ + \n :global CertificateAvailable;\r\ + \n :global CharacterReplace;\r\ + \n :global EitherOr;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global ProtocolStrip;\r\ + \n :global SymbolForNotification;\r\ + \n :global UrlEncode;\r\ + \n :local EscapeMD do={\r\ + \n :local Text [ :tostr \$1 ];\r\ + \n :local Mode [ :tostr \$2 ];\r\ + \n :local Excl [ :tostr \$3 ];\r\ + \n :global CharacterReplace;\r\ + \n :global IfThenElse;\r\ + \n :local Chars {\r\ + \n \"body\"={ \"\\\\\"; \"`\" };\r\ + \n \"plain\"={ \"_\"; \"*\"; \"[\"; \"]\"; \"(\"; \")\"; \"~\"; \"`\"\ + ; \">\";\r\ + \n \"#\"; \"+\"; \"-\"; \"=\"; \"|\"; \"{\"; \"}\"; \".\"; \ + \"!\" };\r\ + \n }\r\ + \n :foreach Char in=(\$Chars->\$Mode) do={\r\ + \n :if ([ :typeof [ :find \$Excl \$Char ] ] = \"nil\") do={\r\ + \n :set Text [ \$CharacterReplace \$Text \$Char (\"\\\\\" . \$Char)\ + \_];\r\ + \n }\r\ + \n }\r\ + \n :if (\$Mode = \"body\") do={:return (\"```\\n\" . \$Text . \"\\n```\ + \")}\r\ + \n :return \$Text;\r\ + \n }\r\ + \n :local ChatId [ \$EitherOr (\$Notification->\"chatid\") \\\r\ + \n [ \$EitherOr (\$TelegramChatIdOverride->(\$Notification->\"origin\")\ + ) \$TelegramChatId ] ];\r\ + \n :local ThreadId [ \$EitherOr (\$Notification->\"threadid\") \\\r\ + \n [ \$EitherOr (\$TelegramThreadIdOverride->(\$Notification->\"origin\ + \")) \\\r\ + \n [ \$IfThenElse ([ :len (\$TelegramChatIdOverride->(\$Notification->\ + \"origin\")) ] = 0) \$TelegramThreadId ] ] ];\r\ + \n :local TokenId [ \$EitherOr (\$TelegramTokenIdOverride->(\$Notificatio\ + n->\"origin\")) \$TelegramTokenId ];\r\ + \n :if ([ :len \$TokenId ] = 0 || [ :len \$ChatId ] = 0) do={:return fals\ + e}\r\ + \n :if ([ :typeof \$TelegramMessageIDs ] = \"nothing\") do={:set Telegram\ + MessageIDs ({})}\r\ + \n :local Truncated false;\r\ + \n :local Text (\"*__\" . [ \$EscapeMD (\"[\" . \$IdentityExtra . \$Ident\ + ity . \"] \" . \\\r\ + \n (\$Notification->\"subject\")) \"plain\" ] . \"__*\\n\\n\");\r\ + \n :local LenSubject [ :len \$Text ];\r\ + \n :local LenMessage [ :len (\$Notification->\"message\") ];\r\ + \n :local LenLink ([ :len (\$Notification->\"link\") ] * 2);\r\ + \n :local LenSum (\$LenSubject + \$LenMessage + \$LenLink);\r\ + \n :if (\$LenSum > 3968) do={\r\ + \n :set Text (\$Text . [ \$EscapeMD ([ :pick (\$Notification->\"message\ + \") 0 (3840 - \$LenSubject - \$LenLink) ] . \"...\") \"body\" ]);\r\ + \n :set Truncated true;\r\ + \n } else={:set Text (\$Text . [ \$EscapeMD (\$Notification->\"message\")\ + \_\"body\" ])}\r\ + \n :if (\$LenLink > 0) do={\r\ + \n :set Text (\$Text . \"\\n\" . [ \$SymbolForNotification \"link\" ] .\ + \_\\\r\ + \n \"[\" . [ \$EscapeMD [ \$ProtocolStrip (\$Notification->\"link\") \ + ] \"plain\" ] . \"]\" . \\\r\ + \n \"(\" . [ \$EscapeMD (\$Notification->\"link\") \"plain\" ] . \")\ + \");\r\ + \n }\r\ + \n :if (\$Truncated = true) do={\r\ + \n :set Text (\$Text . \"\\n\" . [ \$SymbolForNotification \"scissors\"\ + \_] . \\\r\ + \n [ \$EscapeMD (\"The message was too long and has been truncated, c\ + ut off _\" . \\\r\ + \n ((\$LenSum - [ :len \$Text ]) * 100 / \$LenSum) . \"%_!\") \"plain\ + \" \"_\" ]);\r\ + \n }\r\ + \n :local HTTPData (\"chat_id=\" . \$ChatId . \"&disable_notification=\" \ + . (\$Notification->\"silent\") . \\\r\ + \n \"&reply_to_message_id=\" . (\$Notification->\"replyto\") . \"&mes\ + sage_thread_id=\" . \$ThreadId . \\\r\ + \n \"&disable_web_page_preview=true&parse_mode=MarkdownV2\");\r\ + \n :onerror Err {\r\ + \n :if ([ \$CertificateAvailable \"Go Daddy Root Certificate Authority \ + - G2\" \"fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Downloading required certificate failed.\ + \");\r\ + \n :error false;\r\ + \n }\r\ + \n :local Data ([ /tool/fetch check-certificate=yes-without-crl output=\ + user http-method=post \\\r\ + \n (\"https://api.telegram.org/bot\" . \$TokenId . \"/sendMessage\") \ + \\\r\ + \n http-data=(\$HTTPData . \"&text=\" . [ \$UrlEncode \$Text ]) as-va\ + lue ]->\"data\");\r\ + \n :set (\$TelegramMessageIDs->[ :tostr ([ :deserialize from=json value\ + =\$Data ]->\"result\"->\"message_id\") ]) 1;\r\ + \n } do={\r\ + \n \$LogPrint info \$0 (\"Failed sending Telegram notification: \" . \$\ + Err . \" - Queuing...\");\r\ + \n :if ([ :typeof \$TelegramQueue ] = \"nothing\") do={:set TelegramQue\ + ue ({})}\r\ + \n :set Text (\$Text . \"\\n\" . [ \$SymbolForNotification \"alarm-cloc\ + k\" ] . \\\r\ + \n [ \$EscapeMD (\"This message was queued since _\" . [ /system/cloc\ + k/get date ] . \\\r\ + \n \" \" . [ /system/clock/get time ] . \"_ and may be obsolete.\") \ + \"plain\" \"_\" ]);\r\ + \n :set (\$TelegramQueue->[ :len \$TelegramQueue ]) { tokenid=\$TokenId\ + ;\r\ + \n http-data=(\$HTTPData . \"&text=\" . [ \$UrlEncode \$Text ]) };\r\ + \n :if ([ :len [ /system/scheduler/find where name=\"_FlushTelegramQueu\ + e\" ] ] = 0) do={\r\ + \n /system/scheduler/add name=\"_FlushTelegramQueue\" interval=1m sta\ + rt-time=startup \\\r\ + \n on-event=(\":global FlushTelegramQueue; \\\$FlushTelegramQueue;\ + \");\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n# purge the Telegram queue\r\ + \n:set PurgeTelegramQueue do={\r\ + \n :global TelegramQueue;\r\ + \n /system/scheduler/remove [ find where name=\"_FlushTelegramQueue\" ];\ + \r\ + \n :set TelegramQueue;\r\ + \n}\r\ + \n# send notification via telegram - expects at least two string arguments\ + \r\ + \n:set SendTelegram do={ :onerror Err {\r\ + \n :global SendTelegram2;\r\ + \n \$SendTelegram2 ({ origin=\$0; subject=\$1; message=\$2; link=\$3; sil\ + ent=\$4 });\r\ + \n} do={\r\ + \n :global ExitOnError; \$ExitOnError \$0 \$Err;\r\ + \n} }\r\ + \n# send notification via telegram - expects one array argument\r\ + \n:set SendTelegram2 do={\r\ + \n :local Notification \$1;\r\ + \n :global NotificationFunctions;\r\ + \n (\$NotificationFunctions->\"telegram\") (\"\\\$NotificationFunctions->\ + \\\"telegram\\\"\") \$Notification;\r\ + \n}\r\ + \n" +add dont-require-permissions=no name=testIfThenElseAndEitherOr owner=vados \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source=":local ExitOK false;\r\ + \n:local ScriptName [ :jobname ];\r\ + \n\r\ + \n:local ExitError;\r\ + \n#:local IfThenElse;\r\ + \n#:local EitherOr;\r\ + \n\r\ + \n:local one 11;\r\ + \n:local two 8;\r\ + \n#:log warning ([ \$IfThenElse (\$one != 0) \$one \$two ]);\r\ + \n:local Test; # [ :totime \"22s\" ];\r\ + \n\r\ + \n:local IfThenElse do={:if ([ :tostr \$1 ] = \"true\" || [ :tobool \$1 ] \ + = true) do={:return \$2}; :return \$3;}\r\ + \n:local EitherOr do={\r\ + \n :if ([ :typeof \$1 ] = \"num\") do={:if ((\$1 != 0) = true) do={:retur\ + n \$1} else={:return \$2};}\r\ + \n :if ([ :typeof \$1 ] = \"time\") do={:if ((\$1 > 0s) = true) do={:retu\ + rn \$1} else={:return \$2};}\r\ + \n :if ([ :len [ :tostr \$1 ] ] > 0) do={:return \$1} else={:return \$2};\ + \r\ + \n}\r\ + \n\r\ + \n:log warning ([ \$IfThenElse (\$one != 0) \$one \$two ]);\r\ + \n\r\ + \n:set Test [ \$EitherOr \$Test \"\" ];\r\ + \n:log warning (\"\\\$ Test = \" . \$Test);\r\ + \n\r\ + \n:set ExitError do={\r\ + \n :local ExitOK [ :tostr \$1 ];\r\ + \n :local Name [ :tostr \$2 ];\r\ + \n :local Error [ :tostr \$3 ];\r\ + \n :local IfThenElse;\r\ + \n :if (\$ExitOK = \"false\") do={\r\ + \n :log error (\$Name [ \$IfThenElse ([ :pick \$Name 0 1 ] = \"\\\$\") \ + \\\r\ + \n \"Function\" \"Script\" ] . \" '\" . \$Name . \"' exited with er\ + ror\" . \\\r\ + \n [ \$IfThenElse (!(\$Error ~ \"^(|true|false)\\\$\")) (\": \" . \ + \$Error) \".\" ]);\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n\r\ + \n#:log warning ([ \$IfThenElse (\$one != 0) \$one \$two ]);\r\ + \n\r\ + \n:set ExitOK true;\r\ + \n\$ExitError \$ExitOK \$ScriptName \"Err test custom\";" +add comment="Update and Backup send notification to EMail" \ + dont-require-permissions=no name=AM-Backup-UpdateEmail owner=\ + AM-Backup-UpdateEmail policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: AM-Backup-UpdateEmail\r\ + \n# Script comment: Update and Backup send notification to EMail\r\ + \n# Copyright (c) 2007-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version>=6.43.7\r\ + \n# requires device-mode, fetch\r\ + \n#\r\ + \n# Updated: 15/04/2025\r\ + \n# Website: https://github.com/beeyev\r\ + \n# Notification e-mail (Make sure you have configured Email settings in T\ + ools -> Email)\r\ + \n:local ExitOK false;\r\ + \n:onerror Err {\r\ + \n :global GlobalConfReady; :global GlobalFuncReady;\r\ + \n :retry { :if (\$GlobalConfReady != true || \$GlobalFuncReady != true) \ + \\\r\ + \n do={ :error (\"Global config and/or functions not ready.\"); }; } \ + delay=500ms max=50;\r\ + \n :local ScriptName [ :jobname ];\r\ + \n\r\ + \n:local IfThenElse do={:if ([ :tostr \$1 ] = \"true\" || [ :tobool \$1 ] \ + = true) do={:return \$2}; :return \$3;}\r\ + \n:local EitherOr do={\r\ + \n :if ([ :typeof \$1 ] = \"num\") do={:if ((\$1 != 0) = true) do={:retur\ + n \$1} else={:return \$2};}\r\ + \n :if ([ :typeof \$1 ] = \"time\") do={:if ((\$1 > 0s) = true) do={:retu\ + rn \$1} else={:return \$2};}\r\ + \n :if ([ :len [ :tostr \$1 ] ] > 0) do={:return \$1} else={:return \$2};\ + \r\ + \n}\r\ + \n:local ExitError do={\r\ + \n :local ExitOK [ :tostr \$1 ];\r\ + \n :local Name [ :tostr \$2 ];\r\ + \n :local Error [ :tostr \$3 ];\r\ + \n :if (\$ExitOK = \"false\") do={:log error (\$Name [ \$IfThenElse ([ :p\ + ick \$Name 0 1 ] = \"\\\$\") \\\r\ + \n \"Function\" \"Script\" ] . \" '\" . \$Name . \"' exited with er\ + ror\" . \\\r\ + \n [ \$IfThenElse (!(\$Error ~ \"^(|true|false)\\\$\")) (\": \" . \ + \$Error) \".\" ]);}\r\ + \n}\r\ + \n\r\ + \n:global LogPrint;\r\ + \n:global EditSchedulers;\r\ + \n:global DevInfoIncludeIP;\r\ + \n\r\ + \n:local SchedInterval \"7d\";\r\ + \n:local SchedStartTime \"03:41:27\";\r\ + \n:local SchedComment \"Every 7 Day Check For Update and Backup send notif\ + ication to EMail.\";\r\ + \n\r\ + \n# Function to add or edit schedulers for scripts, to ensure they are set\ + \_up correctly\r\ + \n# How to use:\r\ + \n# Call this function with the following parameters:\r\ + \n# 1. \$1 - Scheduler name (usually same as script name) [*]\r\ + \n# 2. \$2 - Interval (e.g. \"1d\" (default is \"1d\")) [**]\r\ + \n# 3. \$3 - Start Time (e.g. \"00:00:00\", default is current time) [**]\ + \r\ + \n# 4. \$4 - Comment (optional, default: \"Scheduler for script: \")\ + \_[**]\r\ + \n# 5. \$5 - On Event command (e.g. \"/system/script { run ; }\" (de\ + fault is \"/system/script { run \$SchedName; }\")) [**]\r\ + \n# 6. \$6 - Policy (e.g. \"ftp,reboot,read,write,policy,test,password,sni\ + ff,sensitive,romon\" (default is the same)) [**]\r\ + \n# 7. \$7 - Start Date (e.g. \"jan/01/1970\", default is current date) [*\ + *]\r\ + \n# Notes:\r\ + \n# * - Scheduler name is required, and is used to identify the scheduler\ + \_to create or edit. It is usually the same as the script name (\$ScriptNa\ + me).\r\ + \n# ** - If optional parameters are not provided, default values will be u\ + sed.\r\ + \n# This allows for quick setup with minimal parameters, while still \ + providing flexibility for customization.\r\ + \n# Example usage:\r\ + \n# :if ([ \$EditSchedulers \"my-script\" ] = false) do={:set ExitOK false\ + ; :error true;}\r\ + \n# :if ([ \$EditSchedulers \"my-script\" \"1d\" \"03:41:27\" \"My custom \ + scheduler comment\" false false \"jan/01/1970\" ] = false) do={:set ExitOK\ + \_false; :error true;}\r\ + \n\r\ + \n:set EditSchedulers do={\r\ + \n :local SchedName [ :tostr \$1 ];\r\ + \n :if ([ :len \$SchedName ] = 0) do={ :return false; }\r\ + \n :local Interval [ :tostr \$2 ];\r\ + \n :if ([ :len \$Interval ] = 0) do={ :set Interval \"1d\"; }\r\ + \n :local StartTime [ :tostr \$3 ];\r\ + \n :if ([ :len \$StartTime ] = 0) do={ :set StartTime [/system/clock/get \ + time]; }\r\ + \n :local Comment [ :tostr \$4 ];\r\ + \n :if ([ :len \$Comment ] = 0) do={ :set Comment \"Scheduler for script:\ + \_\$SchedName\"; }\r\ + \n :local OnEvent [ :tostr \$5 ];\r\ + \n :if ([ :len \$OnEvent ] = 0) do={ :set OnEvent \"/system/script { run \ + \$SchedName; }\"; }\r\ + \n :local Policy [ :tostr \$6 ];\r\ + \n :if ([ :len \$Policy ] = 0) do={ :set Policy \"ftp,reboot,read,write,p\ + olicy,test,password,sniff,sensitive,romon\"; }\r\ + \n :local StartDate [ :tostr \$7 ];\r\ + \n :if ([ :len \$StartDate ] = 0) do={ :set StartDate [/system/clock/get \ + date]; }\r\ + \n\r\ + \n :onerror Err {\r\ + \n # Check if scheduler exists, if not - create it\r\ + \n :if ([ :len [ /system/scheduler/find where name=\$SchedName ] ] = 0)\ + \_do={\r\ + \n :log warning (\$SchedName . \"System Scheduler for script: \" . \$\ + SchedName . \" is NOT SET! Fix It Now!\");\r\ + \n /system/scheduler/add comment=\$Comment interval=\$Interval name=\ + \$SchedName on-event=\$OnEvent policy=\$Policy start-date=\$StartDate star\ + t-time=\$StartTime;\r\ + \n }\r\ + \n # Check if scheduler parameters are correct, if not - fix them\r\ + \n :if ([ /system/scheduler/get \$SchedName comment ] != \$Comment) do=\ + {\r\ + \n /system/scheduler/set \$SchedName comment=\$Comment;\r\ + \n }\r\ + \n :if ([ /system/scheduler/get \$SchedName interval ] != \$Interval) d\ + o={\r\ + \n /system/scheduler/set \$SchedName interval=\$Interval;\r\ + \n }\r\ + \n :if ([ /system/scheduler/get \$SchedName on-event ] != \$OnEvent) do\ + ={\r\ + \n /system/scheduler/set \$SchedName on-event=\$OnEvent;\r\ + \n }\r\ + \n :if ([ /system/scheduler/get \$SchedName policy ] != \$Policy) do={\ + \r\ + \n /system/scheduler/set \$SchedName policy=\$Policy;\r\ + \n }\r\ + \n :if ([ /system/scheduler/get \$SchedName start-date ] != \$StartDate\ + ) do={\r\ + \n /system/scheduler/set \$SchedName start-date=\$StartDate;\r\ + \n }\r\ + \n :if ([ /system/scheduler/get \$SchedName start-time ] != \$StartTime\ + ) do={\r\ + \n /system/scheduler/set \$SchedName start-time=\$StartTime;\r\ + \n }\r\ + \n } do={:log error (\$SchedName . \"Failed to add/modify scheduler for '\ + \" . \$SchedName . \"' failed: \" . \$Err); :return false};\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:if ([ \$EditSchedulers \$ScriptName \$SchedInterval \$SchedStartTime \$\ + SchedComment ] = false) do={\r\ + \n :log error (\$ScriptName . \"Failed to add/modify scheduler for \" . \ + \$ScriptName . \"!\");\r\ + \n :set ExitOK true;\r\ + \n :error true;\r\ + \n}\r\ + \n\r\ + \n# Local Function CheckOSUpdateMode\r\ + \n:local CheckOSUpdateMode;\r\ + \n:set CheckOSUpdateMode do={\r\ + \n :local NewMode [ :tostr \$1 ];\r\ + \n :if ([ :len \$NewMode ] = 0) do={\r\ + \n :log error (\$0 . \"/nFunction /\$CheckOSUpdateMode received empty v\ + alue!\");\r\ + \n :return false;\r\ + \n :error true;\r\ + \n }\r\ + \n\r\ + \n :global BackupAndUpdateMode;\r\ + \n :global OldMode [ :tostr \$BackupAndUpdateMode ];\r\ + \n :if (\$NewMode != \$BackupAndUpdateMode && \$NewMode = \"updateOnce\")\ + \_do={\r\ + \n :set BackupAndUpdateMode [ :tostr \$NewMode ];\r\ + \n :log info (\$0 . \"/nOk. /\$BackupAndUpdateMode set to: '\" . \$NewM\ + ode . \"'.\\nYou're a good boy! ))\\nI'm launching the update in 5 seconds\ + .\");\r\ + \n :delay 5;\r\ + \n /system/script { run \$ScriptName; };\r\ + \n } else={\r\ + \n :log warning (\$0 . \"/nFunction /\$CheckOSUpdateMode received value: \ + '\" . \$NewMode . \"', but is may be only \\\"updateOnce\\\"!\");\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n :error false;\r\ + \n}\r\ + \n\r\ + \n# :if ([ :len [ /system/scheduler/find where name=\"running-from-backup\ + -partition\" ] ] > 0) do={\r\ + \n# \$LogPrint warning \$ScriptName (\"Running from backup partition, r\ + efusing to act.\");\r\ + \n# :set ExitOK true;\r\ + \n# :error false;\r\ + \n# }\r\ + \n\r\ + \n:global EmailGeneralTo;\r\ + \n:global BackupAndUpdateMode;\r\ + \n:global forceBackup;\r\ + \n:global BackupPassword;\r\ + \n:global BackupSens;\r\ + \n:global updateChannel;\r\ + \n\r\ + \n:global DevInfoIncludeIP;\r\ + \n\r\ + \n# Installs patch updates only (BackupAndUpdateMode = \"osupdate\").\r\ + \n# Works for `stable` and `long-term` channels.\r\ + \n# Updates only if MAJOR.MINOR match (e.g. 6.43.2 > 6.43.6 allowed, 6.44.\ + 1 skipped).\r\ + \n# Sends info if a newer (non-patch) version is found.\r\ + \n:local installOnlyPatchUpdates false;\r\ + \n:local scriptVersion \"26.04.30\";\r\ + \n:local copyrightUrl \"https://www.github.com/vados-dev/\";\r\ + \n#Script messages prefix\r\ + \n:local SMP \"BkpAndUpdate:\";\r\ + \n\r\ + \n:local exitErrorMessage \"\$SMP script stopped due to an error. Please c\ + heck logs for more details.\";\r\ + \n:log info \"\\n\\n\$SMP Script \\\"Mikrotik RouterOS automatic backup & \ + update\\\" v.\$scriptVersion started.\";\r\ + \n:log info \"\$SMP Script Mode: `\$BackupAndUpdateMode`, Update channel: \ + `\$updateChannel`, Force backup: `\$forceBackup`, Install only patch updat\ + es: `\$installOnlyPatchUpdates`\";\r\ + \n\r\ + \n## vv FUNCTIONS vv ##\r\ + \n:global WaitCheckUpdates; \$WaitCheckUpdates;\r\ + \n:global MiniDateTimeStamp;\r\ + \n:local MDTSFix [\$MiniDateTimeStamp];\r\ + \n:global Identity;\r\ + \n:global IdentityShort;\r\ + \n:global devBoardName;\r\ + \n:global runningVersion;\r\ + \n:global runningChannel;\r\ + \n:global devModel;\r\ + \n:global devRbSerialNumber;\r\ + \n:global ROsVerAvail;\r\ + \n:global PkgUpdStatus;\r\ + \n\r\ + \n# Checks if two RouterOS version strings differ only by the patch versio\ + n\r\ + \n# :put [\$FuncIsPatchUpdateOnly \"6.2.1\" \"6.2.4\"] # Output: true\r\ + \n# :put [\$FuncIsPatchUpdateOnly \"6.2.1\" \"6.3.1\"] # Output: false\r\ + \n:local FuncIsPatchUpdateOnly do={\r\ + \n :local ver1 \$1;\r\ + \n :local ver2 \$2;\r\ + \n # Extract the major and minor components from a version\r\ + \n :local extractMajorMinor do={\r\ + \n :local ver \$1;\r\ + \n :local dot1 [:find \$ver \".\"];\r\ + \n :if (\$dot1 = -1) do={:return \$ver}\r\ + \n :local major [:pick \$ver 0 \$dot1];\r\ + \n :local rest [:pick \$ver (\$dot1 + 1) [:len \$ver]];\r\ + \n :local dot2 [:find \$rest \".\"];\r\ + \n :local minor \$rest;\r\ + \n :if (\$dot2 >= 0) do={:set minor [:pick \$rest 0 \$dot2]}\r\ + \n :return (\$major . \".\" . \$minor);\r\ + \n }\r\ + \n\r\ + \n# Compare the major and minor components of both version strings\r\ + \n:if ([\$extractMajorMinor \$ver1] = [\$extractMajorMinor \$ver2]) do={:r\ + eturn true}\r\ + \n :return false;\r\ + \n}\r\ + \n# Creates backups and returns array of names\r\ + \n# Possible arguments:\r\ + \n# \$1 - file name, without extension\r\ + \n# \$2 - password (optional)\r\ + \n# \$3 - sensitive data in config (optional, default: false)\r\ + \n# Example:\r\ + \n#:put [\$FuncCreateBackups \$backupName]\r\ + \n:local FuncCreateBackups do={\r\ + \n :local backupName [ :tostr \$1 ];\r\ + \n :local BackPass \$2;\r\ + \n :local BackSens \$3;\r\ + \n\r\ + \n #Script messages prefix\r\ + \n :local SMP \"BkpAndUpdate:\";\r\ + \n :local exitErrorMessage \"\$SMP script stopped due to an error. Please\ + \_check logs for more details.\";\r\ + \n :log info (\"\$SMP global function `FuncCreateBackups` started, input:\ + \_`\$backupName`\");\r\ + \n\r\ + \n # validate required parameter: backupName\r\ + \n :if ([:typeof \$backupName] != \"str\" or [:len \$backupName] = 0) do=\ + {\r\ + \n :log error \"\$SMP parameter 'backupName' is required and must be a \ + non-empty string\";\r\ + \n# :log warning \"\$SMP parameter '\\\$backupName' is required and mus\ + t be a non-empty string.\\nI set '\\\$backupName' to 'default-backup'\";\r\ + \n# :set \$backupName \"default-backup\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n\r\ + \n :local backupFileSys \"\$backupName.backup\";\r\ + \n :local backupFileConfig \"\$backupName.rsc\";\r\ + \n :local backupNames {\$backupFileSys;\$backupFileConfig};\r\ + \n\r\ + \n ## Perform system backup\r\ + \n :if ([:len \$BackPass] = 0) do={\r\ + \n :log info (\"\$SMP starting backup without password, backup name: `\ + \$backupName`\");\r\ + \n /system backup save dont-encrypt=yes name=\$backupName;\r\ + \n } else={\r\ + \n :log info (\"\$SMP starting backup with password, backup name: `\$ba\ + ckupName`\");\r\ + \n /system backup save password=\$BackPass name=\$backupName;\r\ + \n }\r\ + \n :log info (\"\$SMP system backup created: `\$backupFileSys`\");\r\ + \n ## Export config file\r\ + \n :if (\$BackSens = true) do={\r\ + \n :log info (\"\$SMP starting export config with sensitive data, backu\ + p name: `\$backupName`\");\r\ + \n # Since RouterOS v7 it needs to be explicitly set that we want to ex\ + port sensitive data\r\ + \n :if ([:pick [/system resource get version] 0 1] < 7) do={\r\ + \n :execute \"/export compact terse file=\$backupName\";\r\ + \n } else={\r\ + \n :execute \"/export compact show-sensitive terse file=\$backupName\ + \";\r\ + \n }\r\ + \n } else={\r\ + \n :log info (\"\$SMP starting export config without sensitive data, ba\ + ckup name: `\$backupName`\");\r\ + \n /export compact hide-sensitive terse file=\$backupName;\r\ + \n }\r\ + \n :log info (\"\$SMP Config export complete: `\$backupFileConfig`\");\r\ + \n :log info (\"\$SMP Waiting a little to ensure backup files are written\ + \");\r\ + \n :delay 20;\r\ + \n :if ([:len [/file find name=\$backupFileSys]] > 0) do={\r\ + \n :log info (\"\$SMP system backup file successfully saved to the file\ + \_system: `\$backupFileSys`\");\r\ + \n } else={\r\ + \n :log error (\"\$SMP system backup was not created, file does not exi\ + st: `\$backupFileSys`\");\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n :if ([:len [/file find name=\$backupFileConfig]] > 0) do={\r\ + \n :log info (\"\$SMP config backup file successfully saved to the file\ + \_system: `\$backupFileConfig`\");\r\ + \n } else={\r\ + \n :log error (\"\$SMP config backup was not created, file does not exi\ + st: `\$backupFileConfig`\");\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n :log info (\"\$SMP global function `FuncCreateBackups` finished. Creat\ + ed backups, system: `\$backupFileSys`, config: `\$backupFileConfig`\")\r\ + \n :return \$backupNames;\r\ + \n}\r\ + \n# Sends an email\r\ + \n# Parameters:\r\ + \n# \$1 - to (email address)\r\ + \n# \$2 - subject\r\ + \n# \$3 - body\r\ + \n# \$4 - file attachments (optional; pass \"\" if not needed)\r\ + \n#\r\ + \n# Example:\r\ + \n# \$FuncSendEmailSafe \"admin@domain.com\" \"Backup Done\" \"Backup comp\ + lete.\" \"backup1.backup\"\r\ + \n:local FuncSendEmailSafe do={\r\ + \n :global EmailGeneralTo;\r\ + \n :local emailSubject \$2;\r\ + \n :local emailBody \$3;\r\ + \n :local emailAttachments \$4;\r\ + \n :local SMP \"Bkp&Upd:\";\r\ + \n :local exitErrorMessage \"\$SMP script stopped due to an error. Please\ + \_check logs for more details.\";\r\ + \n :log info \"\$SMP Attempting to send email to `\$EmailGeneralTo`\";\r\ + \n # SAFETY: wait for any previously queued email to finish\r\ + \n :local waitTimeoutPre 60;\r\ + \n :local waitCounterPre 0;\r\ + \n :while (([/tool e-mail get last-status] = \"resolving-dns\" or [/tool \ + e-mail get last-status] = \"in-progress\")) do={\r\ + \n :if (\$waitCounterPre >= \$waitTimeoutPre) do={\r\ + \n :log error \"\$SMP Email send aborted: previous send did not compl\ + ete after \$waitTimeoutPre seconds\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n :log info \"\$SMP Waiting for previous email to finish (status: \$[/\ + tool e-mail get last-status])...\";\r\ + \n :delay 1; \r\ + \n :set waitCounterPre (\$waitCounterPre + 1);\r\ + \n }\r\ + \n # Send the email\r\ + \n :do {\r\ + \n /tool e-mail send to=\$EmailGeneralTo subject=\$emailSubject body=\$\ + emailBody file=\$emailAttachments;\r\ + \n } on-error={\r\ + \n :log error \"\$SMP Email send command failed to execute. Check logs \ + and verify email settings.\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n # Wait for send status to change from \"in-progress\" / \"resolving-dn\ + s\"\r\ + \n :local waitTimeout 60;\r\ + \n :local waitCounter 0;\r\ + \n :local emailStatus \"\";\r\ + \n :log info \"\$SMP Waiting for email to be sent, timeout in `\$waitTime\ + out` seconds...\";\r\ + \n :while (\$waitCounter < \$waitTimeout) do={\r\ + \n :set emailStatus [/tool e-mail get last-status];\r\ + \n :if (\$emailStatus != \"in-progress\" and \$emailStatus != \"resolvi\ + ng-dns\") do={\r\ + \n :log info \"\$SMP Email send status received: \$emailStatus\";\r\ + \n # exit loop\r\ + \n :set waitCounter \$waitTimeout;\r\ + \n } else={:delay 1; :set waitCounter (\$waitCounter + 1)}\r\ + \n }\r\ + \n # Final decision based on last status\r\ + \n :if (\$emailStatus = \"succeeded\") do={\r\ + \n :log info \"\$SMP Email successfully sent to `\$EmailGeneralTo`\";\ + \r\ + \n } else={\r\ + \n :log error \"\$SMP Email failed to send. Status: `\$emailStatus`. Ch\ + eck logs for more details and verify email settings.\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n}\r\ + \n# Global variable to track current update step\r\ + \n# They need to be initialized here first to be available in the script\r\ + \n:global buGlobalVarTargetOsVersion;\r\ + \n:global buGlobalVarScriptStep;\r\ + \n:local scriptStep \$buGlobalVarScriptStep;\r\ + \n:do {\r\ + \n /system/script/environment remove buGlobalVarScriptStep;\r\ + \n} on-error={}\r\ + \n:if ([:len \$scriptStep] = 0) do={\r\ + \n :set scriptStep 1;\r\ + \n}\r\ + \n## ^^ FUNCTIONS ^^ ##\r\ + \n#\r\ + \n# Initial validation\r\ + \n## Check email settings\r\ + \n:if ([:len \$EmailGeneralTo] < 3) do={\r\ + \n :log error (\"\$SMP Parameter `\\\$EmailGeneralTo` is not set, or cont\ + ains invalid value. Script stopped.\");\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n# Values will be defined later in the script\r\ + \n:local emailServer \"\";\r\ + \n:local emailFromAddress [/tool e-mail get from];\r\ + \n:log info \"\$SMP Validating email settings...\";\r\ + \n:do {\r\ + \n :set emailServer [/tool e-mail get server];\r\ + \n} on-error={\r\ + \n # This is a workaround for the RouterOS v7.12 and older versions\r\ + \n :set emailServer [/tool e-mail get address];\r\ + \n}\r\ + \n:if (\$emailServer = \"0.0.0.0\") do={\r\ + \n :log error (\"\$SMP Email server address is not correct: `\$emailServe\ + r`, check `Tools -> Email`. Script stopped.\");\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n:if ([:len \$emailFromAddress] < 3) do={\r\ + \n :log error (\"\$SMP Email configuration FROM address is not correct: `\ + \$emailFromAddress`, check `Tools -> Email`. Script stopped.\");\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n# Script mode validation\r\ + \n:if (\$BackupAndUpdateMode != \"backup\" and \$BackupAndUpdateMode != \"\ + osupdate\" and \$BackupAndUpdateMode != \"updateOnce\" and \$BackupAndUpda\ + teMode != \"osnotify\") do={\r\ + \n :log error (\"\$SMP Script parameter `\\\$BackupAndUpdateMode` is not \ + set, or contains invalid value: `\$BackupAndUpdateMode`. Script stopped.\"\ + );\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n# Update channel validation\r\ + \n:if (\$updateChannel != \"stable\" and \$updateChannel != \"long-term\" \ + and \$updateChannel != \"testing\" and \$updateChannel != \"development\")\ + \_do={\r\ + \n :log error (\"\$SMP Script parameter `\\\$updateChannel` is not set, o\ + r contains invalid value: `\$updateChannel`. Script stopped.\");\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n# Verify if script is set to install patch updates and if the update cha\ + nnel is valid\r\ + \n:if ((\$BackupAndUpdateMode = \"osupdate\" or \$BackupAndUpdateMode = \"\ + updateOnce\") and \$installOnlyPatchUpdates = true) do={\r\ + \n :if (\$updateChannel != \"stable\" and \$updateChannel != \"long-term\ + \") do={\r\ + \n :log error (\"\$SMP Patch-only updates enabled, but update channel `\ + \$updateChannel` is invalid. Only `stable` and `long-term` are supported. \ + Script stopped\");\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n \r\ + \n :if (\$runningChannel != \"stable\" and \$runningChannel != \"long-ter\ + m\") do={\r\ + \n :log error (\"\$SMP Script is set to install only patch updates, but\ + \_the installed RouterOS version is not from `stable` or `long-term` chann\ + el: `\$runningChannel`. Script stopped\");\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n}\r\ + \n#\r\ + \n\r\ + \n:local rawTime [/system clock get time];\r\ + \n:local rawDate [/system clock get date];\r\ + \n\r\ + \n:local deviceOsVerAndChannelRunning [/system/resource/get version];\r\ + \n\r\ + \n:local backupNameTemplate (\"backup_v\" . \$runningVersion . \"_\" .\ + \_\$runningChannel . \"_\" . \$MDTSFix);\r\ + \n:local backupNameBeforeUpdate (\$backupNameTemplate . \"_before_update\"\ + );\r\ + \n:local backupNameAfterUpdate (\$backupNameTemplate . \"_after_update\")\ + ;\r\ + \n\r\ + \n## Email body template\r\ + \n:local mailSubjectPrefix \"\$SMP Device - `\$IdentityShort`\";\r\ + \n:local mailBodyCopyright \"Mikrotik RouterOS automatic backup & update \ + (ver. \$scriptVersion) \\n\$copyrightUrl\";\r\ + \n:local changelogUrl \"Check RouterOS changelog: https://mikrotik.com\ + /download/changelogs/\";\r\ + \n:local mailBodyDeviceInfo \"\";\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"Device information:\")\ + ;\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\n-------------------\ + --\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nName: \$Identity\")\ + ;\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nModel: \$devModel\"\ + );\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nBoard: \$devBoardNa\ + me\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nSerial number: \$de\ + vRbSerialNumber\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nRouterOS version: v\ + \$deviceOsVerAndChannelRunning\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nBuild time: \$[/sys\ + tem/resource/get build-time]\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nRouterboard FW Avai\ + lable: \$ROsVerAvail\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nDevice date-time: \ + \$rawDate \$rawTime (\$[/system/clock/get time-zone-name ])\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nUptime: \$[/system/\ + resource/get uptime]\");\r\ + \n## IP address detection\r\ + \n:if ([ \$DevInfoIncludeIP ] = true) do={\r\ + \n :global GetPubIp;\r\ + \n :local PublicIpAddress [ \$GetPubIp ];\r\ + \n :set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nPublic IP address\ + : \" . \$PublicIpAddress . \".\");\r\ + \n :log info \"\$SMP Public IP address detected: \$PublicIpAddress\";\r\ + \n} \r\ + \n\r\ + \n:local mailAttachments [:toarray \"\"];\r\ + \n:if (\$scriptStep = 1 or \$scriptStep = 3) do={\r\ + \n :if (\$scriptStep = 3) do={\r\ + \n :log info (\"\$SMP Waiting for one minute before continuing to the f\ + inal step.\");\r\ + \n :delay 1m;\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n## STEP 1: Create backups, check for new RouterOS, and send email\r\ + \n## Steps 2\C3\A23 run only if auto-update is enabled and a new version i\ + s available\r\ + \n:if (\$scriptStep = 1) do={\r\ + \n :global BackupPassword;\r\ + \n :global BackupSens;\r\ + \n :local isNewOsUpdateAvailable false;\r\ + \n :local isLatestOsAlreadyInstalled true;\r\ + \n :local isOsNeedsToBeUpdated false;\r\ + \n :local isUpdateCheckSucceeded false;\r\ + \n :local isEmailNeedsToBeSent false;\r\ + \n :local mailSubjectPartAction \"\";\r\ + \n :local mailPtBodyAction \"\";\r\ + \n :local mailPtSubjectBackup \"\";\r\ + \n :local mailPtBodyBackup \"\";\r\ + \n # Checking for new version\r\ + \n :if (\$BackupAndUpdateMode = \"osupdate\" or \$BackupAndUpdateMode = \ + \"osnotify\" or \$BackupAndUpdateMode = \"updateOnce\") do={\r\ + \n :log info (\"\$SMP Setting update channel to `\$updateChannel`\");\r\ + \n /system/package/update/set channel=\$updateChannel;\r\ + \n :log info (\"\$SMP Checking for new RouterOS version. Current instal\ + led version is: `\$runningVersion`\");\r\ + \n \$LogPrint debug \$ScriptName (\"Checking for updates...\");\r\ + \n \r\ + \n# /system/package/update/check-for-updates without-paging as-value;\ + \r\ + \n# /system/package/update check-for-updates;\r\ + \n :delay 5s;\r\ + \n :set PkgUpdStatus [ /system/package/update/get status ];\r\ + \n :if (\$PkgUpdStatus = \"New version is available\") do={\r\ + \n :set ROsVerAvail [/system/package/update/get latest-version];\r\ + \n }\r\ + \n\r\ + \n :if (\$PkgUpdStatus = \"New version is available\") do={\r\ + \n :log info (\"\$SMP New RouterOS version is available: `\$ROsVerAva\ + il`\");\r\ + \n :set isNewOsUpdateAvailable true;\r\ + \n :set isLatestOsAlreadyInstalled false;\r\ + \n :set isUpdateCheckSucceeded true;\r\ + \n :set isEmailNeedsToBeSent true;\r\ + \n :set mailSubjectPartAction \"New RouterOS available\";\r\ + \n :set mailPtBodyAction \"New RouterOS version is available, curren\ + t version: v\$runningVersion, new version: v\$ROsVerAvail. \\n\$changelogU\ + rl\";\r\ + \n } else={\r\ + \n :if (\$PkgUpdStatus = \"System is already up to date\") do={\r\ + \n :log info (\"\$SMP No new RouterOS version is available, the lat\ + est version is already installed: `v\$runningVersion`\");\r\ + \n :set isUpdateCheckSucceeded true;\r\ + \n :set mailSubjectPartAction \"No os update available\";\r\ + \n :set mailPtBodyAction \"No new RouterOS version is available, t\ + he latest version is already installed: `v\$runningVersion`\";\r\ + \n } else={\r\ + \n :log error (\"\$SMP Failed to check for new RouterOS version. Pa\ + ckage check status: `\$PkgUpdStatus`\");\r\ + \n :set isEmailNeedsToBeSent true;\r\ + \n :set mailSubjectPartAction \"Error unable to check new os versio\ + n\";\r\ + \n :set mailPtBodyAction \"An error occurred while checking for a \ + new RouterOS version.\\nStatus returned: `\$PkgUpdStatus`\\n\\nPlease revi\ + ew the logs on the device for more details and verify internet connectivit\ + y.\";\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n # Checking if the script needs to install new os version\r\ + \n :if ((\$BackupAndUpdateMode = \"osupdate\" or \$BackupAndUpdateMode = \ + \"updateOnce\") and \$isNewOsUpdateAvailable = true) do={\r\ + \n :if (\$installOnlyPatchUpdates = true) do={\r\ + \n :if ([\$FuncIsPatchUpdateOnly \$runningVersion \$ROsVerAvail] = tr\ + ue) do={\r\ + \n :log info \"\$SMP New RouterOS version is available, and it is a\ + \_patch update. Current version: v\$runningVersion, new version: v\$ROsVer\ + Avail\";\r\ + \n :set isOsNeedsToBeUpdated true;\r\ + \n } else={\r\ + \n :log info \"\$SMP The script will not install this update, becau\ + se it is not a patch update. Current version: v\$runningVersion, new versi\ + on: v\$ROsVerAvail\";\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nThis update wil\ + l not be installed, because the script is set to install only patch update\ + s.\");\r\ + \n }\r\ + \n } else={\r\ + \n :set isOsNeedsToBeUpdated true;\r\ + \n }\r\ + \n }\r\ + \n # Checking If the script needs to create a backup\r\ + \n :if (\$forceBackup = true or \$BackupAndUpdateMode = \"backup\" or \$i\ + sOsNeedsToBeUpdated = true) do={\r\ + \n :log info (\"\$SMP Starting backup process.\");\r\ + \n :set isEmailNeedsToBeSent true;\r\ + \n :local backupName \$backupNameTemplate;\r\ + \n # This means it's the first step where we create a backup before the\ + \_update process\r\ + \n :if (\$isOsNeedsToBeUpdated = true) do={\r\ + \n :set backupName \$backupNameBeforeUpdate;\r\ + \n #Email body if the purpose of the script is to update the device\r\ + \n :set mailSubjectPartAction \"Update preparation\";\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nThe update proces\ + s for device '\$Identity' is scheduled to upgrade RouterOS from version v.\ + \$runningVersion to version v.\$ROsVerAvail (Update channel: \$updateChann\ + el)\");\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nPlease note: The \ + update will proceed only after a successful backup.\");\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nA final report wi\ + th detailed information will be sent once the update process is completed.\ + \");\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nIf you do not rec\ + eive a second email within the next 10 minutes, there may be an issue. Ple\ + ase check your device logs for further information.\");\r\ + \n }\r\ + \n :do {\r\ + \n :set mailAttachments [\$FuncCreateBackups \$backupName \$BackupPas\ + sword \$BackupSens];\r\ + \n :set mailPtSubjectBackup \"Backup created\";\r\ + \n :set mailPtBodyBackup \"System backups have been successfully crea\ + ted and attached to this email.\";\r\ + \n } on-error={\r\ + \n :set isOsNeedsToBeUpdated false;\r\ + \n :set mailPtSubjectBackup \"Backup failed\";\r\ + \n :set mailPtBodyBackup \"The script failed to create backups. Pleas\ + e check device logs for more details.\";\r\ + \n :log warning \"\$SMP Backup creation failed. Update process will b\ + e canceled if automatic update is enabled\";\r\ + \n }\r\ + \n }\r\ + \n :if (\$isEmailNeedsToBeSent = true) do={\r\ + \n :log info \"\$SMP Preparing to send email...\";\r\ + \n :local mailStep1Subject \$mailSubjectPrefix;\r\ + \n :local mailStep1Body \"\";\r\ + \n # subject\r\ + \n :if (\$mailSubjectPartAction != \"\") do={:set mailStep1Subject (\$\ + mailStep1Subject . \" - \" . \$mailSubjectPartAction)}\r\ + \n :if (\$mailPtSubjectBackup != \"\") do={:set mailStep1Subject (\$ma\ + ilStep1Subject . \" - \" . \$mailPtSubjectBackup)}\r\ + \n # body\r\ + \n :if (\$mailPtBodyAction != \"\") do={:set mailStep1Body (\$mailStep1\ + Body . \$mailPtBodyAction . \"\\n\\n\")}\r\ + \n :if (\$mailPtBodyBackup != \"\") do={:set mailStep1Body (\$mailStep1\ + Body . \$mailPtBodyBackup . \"\\n\\n\")}\r\ + \n :set mailStep1Body (\$mailStep1Body . \$mailBodyDeviceInfo . \"\\n__\ + ________\\n\" . \$mailBodyCopyright);\r\ + \n :set mailStep1Body (\$mailStep1Body . \"\\n[*** TEST START ***]\\n\\\ + \$DeviceInfo: \" . [ \$DeviceInfo ] . \"\\n[*** TEST END ***]\\n\");\r\ + \n # Send email with backups\r\ + \n :do {\$FuncSendEmailSafe \$EmailGeneralTo \$mailStep1Subject \$mailS\ + tep1Body \$mailAttachments} on-error={\r\ + \n :set isOsNeedsToBeUpdated false;\r\ + \n :log error \"\$SMP The script will not proceed with the update pro\ + cess, because the email was not sent.\";\r\ + \n }\r\ + \n }\r\ + \n :if ([:len \$mailAttachments] > 0) do={\r\ + \n :log info \"\$SMP Cleaning up backup files from the file system...\"\ + ;\r\ + \n /file remove \$mailAttachments;\r\ + \n :delay 2s;\r\ + \n }\r\ + \n :if (\$isOsNeedsToBeUpdated = true) do={\r\ + \n :global OldMode;\r\ + \n :log info \"\$SMP everything is ready to install new RouterOS, going\ + \_to start the update process and reboot the device.\";\r\ + \n :do {\r\ + \n :local nextStep 2;\r\ + \n :if (\$isCloudHostedRouter = true) do={\r\ + \n :log info \"\$SMP The device is a cloud hosted router, the secon\ + d step updating the Routerboard firmware will be skipped.\";\r\ + \n :set nextStep 3;\r\ + \n }\r\ + \n :local scheduledCommand (\":delay 5s; /system/scheduler remove BKP\ + UPD-NEXT-BOOT-TASK; \\\r\ + \n :global buGlobalVarScriptStep \$nextStep; :global buGlobalVarTarg\ + etOsVersion \\\"\$ROsVerAvail\\\"; \\\r\ + \n :delay 10s; /system/script { run \$ScriptName; }\");\r\ + \n /system/scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=\$schedu\ + ledCommand start-time=startup interval=0;\r\ + \n /system/package/update install;\r\ + \n } on-error={\r\ + \n # Failed to install new os version, remove the task and variables\ + \r\ + \n :do {\r\ + \n /system/scheduler remove BKPUPD-NEXT-BOOT-TASK;\r\ + \n :set BackupAndUpdateMode \$OldMode;\r\ + \n :delay 2;\r\ + \n :do {\r\ + \n :global buGlobalVarTargetOsVersion (a);\r\ + \n } on-error={\r\ + \n :set \$mailSubjectPrefix (\$mailSubjectPrefix . \"ERROR Remove\ + \_global variable \\\$buGlobalVarTargetOsVersion failed!\");\r\ + \n }\r\ + \n# :if ([ :len \$OldMode ] > 0) do={\r\ + \n# :do {\r\ + \n# :global OldMode (a);\r\ + \n# } on-error={\r\ + \n# :set \$mailSubjectPrefix (\$mailSubjectPrefix . \" ERROR Re\ + move global variable \\\$OldMode failed!\");\r\ + \n# }\r\ + \n# }\r\ + \n } on-error={\r\ + \n :log error \"\$SMP Failed to install new RouterOS version. Pleas\ + e check device logs for more details. \\\r\ + \n \\nAnd Failed to remove task and variables! Check it!\";\r\ + \n :set \$mailSubjectPrefix (\$mailSubjectPrefix . \" ERROR Remove \ + task and global variables!\");\r\ + \n }\r\ + \n :log error \"\$SMP Failed to install new RouterOS version. Please \ + check device logs for more details.\";\r\ + \n :local mailUpdateErrorSubject (\$mailSubjectPrefix . \" - Update f\ + ailed\");\r\ + \n :local mailUpdateErrorBody \"The script was unable to install new \ + RouterOS version. Please check device logs for more details.\";\r\ + \n # Send email with error\r\ + \n \$FuncSendEmailSafe \$EmailGeneralTo \$mailUpdateErrorSubject \$ma\ + ilUpdateErrorBody \"\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n## STEP 2: (Post-reboot) Upgrade RouterBOARD firmware\r\ + \n## Runs only if auto-update is enabled and a new RouterOS version was fo\ + und\r\ + \n:if (\$scriptStep = 2) do={\r\ + \n :log info \"\$SMP The script is in the second step, updating Routerboa\ + rd firmware.\";\r\ + \n :log info \"\$SMP Upgrading routerboard firmware from v.\$deviceRbCurr\ + entFw to v.\$deviceRbUpgradeFw\";\r\ + \n /system routerboard upgrade;\r\ + \n :delay 2;\r\ + \n :log info \"\$SMP routerboard upgrade process was completed, going to \ + reboot in a moment!\";\r\ + \n ## Set task to send final report on the next boot\r\ + \n /system scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=\":delay 2; \ + :global buGlobalVarScriptStep 3; \\\r\ + \n :global buGlobalVarTargetOsVersion \\\"\$buGlobalVarTargetOsVersion\\\ + \"; :delay 10s; /system/script { run \$ScriptName; }; \\ \r\ + \n :delay 5s; /system/scheduler remove BKPUPD-NEXT-BOOT-TASK;\" start-ti\ + me=startup interval=0;\r\ + \n :delay 2;\r\ + \n /system reboot;\r\ + \n}\r\ + \n\r\ + \n## STEP 3: Final report (after second reboot, with delay).\r\ + \n## Runs only if auto-update is enabled and a new RouterOS version was fo\ + und.\r\ + \n:if (\$scriptStep = 3) do={\r\ + \n :log info (\"\$SMP The script is in the third step, sending final repo\ + rt.\");\r\ + \n :local targetOsVersion \$buGlobalVarTargetOsVersion;\r\ + \n :do {\r\ + \n# /system/script/environment remove [find name=\"buGlobalVarTargetOsV\ + ersion\"];\r\ + \n :global buGlobalVarTargetOsVersion (a);\r\ + \n } on-error={\r\ + \n :set \$mailSubjectPrefix (\$mailSubjectPrefix . \" ERROR Remove glob\ + al variable \\\$buGlobalVarTargetOsVersion failed!\");\r\ + \n }\r\ + \n\r\ + \n :if ([ :len \$OldMode ] != 0) do={\r\ + \n :do {\r\ + \n# /system/script/environment remove [find name=\"OldMode\"];\r\ + \n :global OldMode (a);\r\ + \n } on-error={\r\ + \n :set \$mailSubjectPrefix (\$mailSubjectPrefix . \" ERROR! Remove\ + \_global variable \\\$OldMode failed!\");\r\ + \n }\r\ + \n }\r\ + \n :if ([:len \$targetOsVersion] = 0) do={\r\ + \n :log warning \"\$SMP Something is wrong, the script was unable to ge\ + t the target updated OS version from the global variable.\";\r\ + \n }\r\ + \n :local mailStep3Subject \$mailSubjectPrefix;\r\ + \n :local mailStep3Body \"\";\r\ + \n :if (\$targetOsVersion = \$runningVersion) do={\r\ + \n :log info \"\$SMP Successfully verified new RouterOS version: target\ + : `\$targetOsVersion`, current: `\$runningVersion`\";\r\ + \n :set mailStep3Subject (\$mailStep3Subject . \" - Update completed - \ + Backup created\");\r\ + \n :set mailStep3Body (\$mailStep3Body . \"RouterOS and routerboard upg\ + rade process was completed\");\r\ + \n :set mailStep3Body (\$mailStep3Body . \"\\nNew RouterOS version: v.\ + \$targetOsVersion, routerboard firmware: v.\$deviceRbCurrentFw\");\r\ + \n :set mailStep3Body (\$mailStep3Body . \"\\n\$changelogUrl\\nBackups \ + of the upgraded system are in the attachment of this email.\\n\\n\$mailBod\ + yDeviceInfo\\n\\n\$mailBodyCopyright\");\r\ + \n :set mailAttachments [\$FuncCreateBackups \$backupNameAfterUpdate \$\ + BackupPassword \$BackupSens];\r\ + \n } else={\r\ + \n :log error \"\$SMP Failed to verify new RouterOS version: target: `\ + \$targetOsVersion`, current: `\$runningVersion`\";\r\ + \n :set mailStep3Subject (\$mailStep3Subject . \" - Update failed\");\r\ + \n :set mailStep3Body (\$mailStep3Body . \"The script was unable to ver\ + ify that the new RouterOS version was installed, target version: `\$target\ + OsVersion`, current version: `\$runningVersion`\\nCheck device logs for mo\ + re details.\\n\\n\$mailBodyDeviceInfo\\n\\n\$mailBodyCopyright\");\r\ + \n }\r\ + \n \$FuncSendEmailSafe \$EmailGeneralTo \$mailStep3Subject \$mailStep3Bod\ + y \$mailAttachments;\r\ + \n :if ([:len \$mailAttachments] > 0) do={\r\ + \n :log info \"\$SMP Cleaning up backup files from the file system...\"\ + ;\r\ + \n /file remove \$mailAttachments;\r\ + \n :delay 2;\r\ + \n }\r\ + \n :log info (\"\$SMP Final report email sent successfully, and the scrip\ + t has finished.\");\r\ + \n}\r\ + \n:log info (\"\$SMP the script has finished, script step: `\$scriptStep` \ + \\n\\n\");\r\ + \n\r\ + \n} do={:global ExitError; \$ExitError \$ExitOK [ :jobname ] \$Err}\r\ + \n" +add dont-require-permissions=no name=testGetPubIP owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":\ + global DevInfoIncludeIP;\r\ + \n:global CharacterReplace;\r\ + \n:global EscapeForRegEx;\r\ + \n:global UrlEncode;\r\ + \n\r\ + \n:if ([ \$DevInfoIncludeIP ] = true) do={\r\ + \n :global GetPubIp;\r\ + \n :local Char (\"\\n\\r\");\r\ + \n :local PublicIpAddress [\$GetPubIp];\r\ + \n# :if ([ :pick \$PublicIpAddress ([ :len \$PublicIpAddress ] - 2) ] = \ + \"\\n\") do={:error true}; :set error falseon-error={:set PublicIpAddress \ + (\$PublicIpAddress . Char)};\r\ + \n#:if ([ :pick \$PublicIpAddress ([ :len \$PublicIpAddress ] - 1) ] = \"/\ + n\") do={\r\ + \n#:log warning \"N is = //n\"; \r\ + \n# :set PublicIpAddress [ \$CharacterReplace \$PublicIpAddress \$Char \"\ + \" ];\r\ + \n#}\r\ + \n# :set PublicIpAddress [ \$UrlEncode \$PublicIpAddress ];\r\ + \n#:local IpLen ([ :len \$PublicIpAddress ] -1);\r\ + \n#:set PublicIpAddress [:pick \$PublicIpAddress 0 \$IpLen];\r\ + \n :log warning \"Public IP address detected: \$PublicIpAddress , len = \ + \$IpLen\";\r\ + \n}\r\ + \n" +add comment="download, import and update firewall address-lists" \ + dont-require-permissions=no name=AM-FwAddrLists owner=AM-FwAddrLists \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source="#!rsc by Vados\r\ + \n# RouterOS script: AM-FwAddrLists\r\ + \n# Script comment: download, import and update firewall address-lists\r\ + \n# Copyright (c) 2007-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# https://ros.vados.ru/doc/fw-addr-lists.md\r\ + \n:local ExitOK false;\r\ + \n:onerror Err {\r\ + \n :global GlobalConfReady; :global GlobalFuncReady;\r\ + \n :retry { :if (\$GlobalConfReady != true || \$GlobalFuncReady != true) \ + \\\r\ + \n do={ :error (\"Global config and/or functions not ready.\"); }; } \ + delay=500ms max=50;\r\ + \n :local ScriptName [ :jobname ];\r\ + \n\r\ + \n :global GlobalScriptsUrl;\r\ + \n :local ListsDirName (\"fw-addr-lists.d/\" . [ /system identity get nam\ + e ] . \"/\");\r\ + \n :local ListsUrl (\$GlobalScriptsUrl . \$ListsDirName);\r\ + \n :local ListsIndex \"index.lst\";\r\ + \n :local AddrListsTimeOut 1d;\r\ + \n :local CheckCerts false;\r\ + \n\r\ + \n :global CertificateAvailable;\r\ + \n :global EitherOr;\r\ + \n :global FetchHugeFiles;\r\ + \n :global HumanReadableNum;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global NetMask4;\r\ + \n :global NetMask6;\r\ + \n :global ScriptLock;\r\ + \n \r\ + \n :local FindDelim do={\r\ + \n :local ValidChars \"0123456789.:/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghi\ + jklmnopqrstuvwxyz-\";\r\ + \n :for I from=0 to=[ :len \$1 ] do={\r\ + \n :if ([ :typeof [ :find \$ValidChars [ :pick (\$1 . \" \") \$I ] ] \ + ] != \"num\") do={\r\ + \n :return \$I;\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :local GetBranch do={\r\ + \n :global EitherOr;\r\ + \n :return [ :pick [ :convert transform=md5 to=hex [ :pick \$1 0 [ \$Ei\ + therOr [ :find \$1 \"/\" ] [ :len \$1 ] ] ] ] 0 2 ];\r\ + \n }\r\ + \n \r\ + \n :if ([ \$ScriptLock \$ScriptName ] = false) do={\r\ + \n :set ExitOK true;\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :local ListComment (\"managed by \" . \$ScriptName);\r\ + \n :local Url (\$ListsUrl . \$ListsIndex);\r\ + \n :local tmpIndex false;\r\ + \n\r\ + \n :for I from=1 to=5 do={\r\ + \n :if (\$tmpIndex = false) do={\r\ + \n :set tmpIndex [ :tolf [ \$FetchHugeFiles \$ScriptName \$Url \$Chec\ + kCerts ] ];\r\ + \n :if (\$tmpIndex = false && \$I < 5) do={\r\ + \n :log warning (\$ScriptName . \" Failed downloading address-list \ + index, \" . \$I . \". try from: \" . \$Url);\r\ + \n :delay ((\$I * \$I) . \"s\");\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n :if (\$tmpIndex = false) do={\r\ + \n :log warning (\$ScriptName . \" Failed downloading address-list inde\ + x from: \" . \$Url);\r\ + \n :set tmpIndex \"\";\r\ + \n }\r\ + \n\r\ + \n :foreach List in=[ :deserialize \$tmpIndex delimiter=\"\\n\" from=dsv \ + options=dsv.plain ] do={\r\ + \n# :set List (\$List->0);\r\ + \n :local ListName;\r\ + \n :local Delim [ \$FindDelim \$List ];\r\ + \n :if (\$Delim = 0) do={ :set List \"\"; };\r\ + \n :log warning (\$ScriptName . \" Parsing address-list index entry: \"\ + \_. \$List);\r\ + \n :if ([ :len \$List ] = 0) do={\r\ + \n :log error (\$ScriptName . \" Failed parsing address-list index en\ + try: \" . \$List);\r\ + \n } else={\r\ + \n :set ListName [ :pick \$List 0 \$Delim ];\r\ + \n }\r\ + \n :log warning (\$ScriptName . \" Parsing address-list index entry: \"\ + \_. \$ListName . \" - Ok.\");\r\ + \n :local ListUrl (\$ListsUrl . \$ListName);\r\ + \n :local CntAdd 0;\r\ + \n :local CntRenew 0;\r\ + \n :local CntRemove 0;\r\ + \n :local IPv4Addresses ({});\r\ + \n :local IPv6Addresses ({});\r\ + \n :local Failure false;\r\ + \n :local Data false;\r\ + \n :local TimeOut \$AddrListTimeOut;\r\ + \n\r\ + \n :for I from=1 to=5 do={\r\ + \n :if (\$Data = false) do={\r\ + \n :set Data [ :tolf [ \$FetchHugeFiles \$ScriptName \$ListUrl \$Ch\ + eckCerts ] ];\r\ + \n :if (\$Data = false && \$I < 5) do={\r\ + \n :log warning (\$ScriptName . \" Failed downloading for list '\ + \" . \$ListName . \"', \" . \$I . \". try from: \" . \$ListUrl);\r\ + \n :delay ((\$I * \$I) . \"s\");\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if (\$Data = false) do={\r\ + \n :set Data \"\";\r\ + \n :set Failure true;\r\ + \n :log warning (\$ScriptName . \" Failed downloading for list '\" . \ + \$ListName . \"' from: \" . \$ListUrl);\r\ + \n } else={\r\ + \n :log warning (\$ScriptName . \" Downloaded \" . [ \$HumanReadableN\ + um [ :len \$Data ] 1024 ] . \"B for list '\" . \$ListName . \"' from: \" .\ + \_\$ListUrl);\r\ + \n }\r\ + \n\r\ + \n :foreach Line in=[ :deserialize \$Data delimiter=\"\\n\" from=dsv op\ + tions=dsv.plain ] do={\r\ + \n :set Line (\$Line->0);\r\ + \n :local Address;\r\ + \n :if ([ :pick \$Line 0 1 ] = \"{\" && [ :pick \$Line ([ :len \$Line\ + \_] - 1) ] = \"}\") do={\r\ + \n :do {\r\ + \n :set Address [ :tostr ([ :deserialize from=json \$Line ]->\"ci\ + dr\") ];\r\ + \n } on-error={ }\r\ + \n } else={\r\ + \n :set Address ([ :pick \$Line 0 [ \$FindDelim \$Line ] ] . (\$L\ + ist->\"cidr\"));\r\ + \n }\r\ + \n :do {\r\ + \n :local Branch;\r\ + \n :if (\$Address ~ \"^[0-9]{1,3}\\\\.[0-9]{1,3}\\\\.[0-9]{1,3}\\\\\ + .[0-9]{1,3}(/[0-9]{1,2})\?\\\$\") do={\r\ + \n :local Net \$Address;\r\ + \n :local CIDR 32;\r\ + \n :local Slash [ :find \$Address \"/\" ];\r\ + \n :if ([ :typeof \$Slash ] = \"num\") do={\r\ + \n :set Net [ :toip [ :pick \$Address 0 \$Slash ] ]\r\ + \n :set CIDR [ :pick \$Address (\$Slash + 1) [ :len \$Address ]\ + \_];\r\ + \n :set Address [ :tostr (([ :toip \$Net ] & [ \$NetMask4 \$CID\ + R ]) . [ \$IfThenElse (\$CIDR < 32) (\"/\" . \$CIDR) ]) ];\r\ + \n }\r\ + \n :set Branch [ \$GetBranch \$Address ];\r\ + \n :set (\$IPv4Addresses->\$Branch->\$Address) \$TimeOut;\r\ + \n }\r\ + \n :if (\$Address ~ \"^[0-9a-zA-Z]*:[0-9a-zA-Z:\\\\.]+(/[0-9]{1,3})\ + \?\\\$\") do={\r\ + \n :local Net \$Address;\r\ + \n :local CIDR 128;\r\ + \n :local Slash [ :find \$Address \"/\" ];\r\ + \n :if ([ :typeof \$Slash ] = \"num\") do={\r\ + \n :set Net [ :toip6 [ :pick \$Address 0 \$Slash ] ]\r\ + \n :set CIDR [ :pick \$Address (\$Slash + 1) [ :len \$Address ]\ + \_];\r\ + \n }\r\ + \n :set Address (([ :toip6 \$Net ] & [ \$NetMask6 \$CIDR ]) . \"/\ + \" . \$CIDR);\r\ + \n :set Branch [ \$GetBranch \$Address ];\r\ + \n :set (\$IPv6Addresses->\$Branch->\$Address) \$TimeOut;\r\ + \n }\r\ + \n :if (\$Address ~ \"^[\\\\.a-zA-Z0-9-]+\\\\.[a-zA-Z]{2,}\\\$\") d\ + o={\r\ + \n :set Branch [ \$GetBranch \$Address ];\r\ + \n :set (\$IPv4Addresses->\$Branch->\$Address) \$TimeOut;\r\ + \n :set (\$IPv6Addresses->\$Branch->\$Address) \$TimeOut;\r\ + \n }\r\ + \n } on-error={ }\r\ + \n }\r\ + \n \r\ + \n\r\ + \n :foreach Entry in=[ /ip/firewall/address-list/find where \\\r\ + \n list=\$ListName comment=\$ListComment ] do={\r\ + \n :local Address [ /ip/firewall/address-list/get \$Entry address ];\ + \r\ + \n :local Branch [ \$GetBranch \$Address ];\r\ + \n :local TimeOut (\$IPv4Addresses->\$Branch->\$Address);\r\ + \n :if ([ :typeof \$TimeOut ] = \"time\") do={\r\ + \n :log warning (\$ScriptName . \" Renewing IPv4 address \" . \$Add\ + ress . \\\r\ + \n \" in list '\" . \$ListName . \"' with \" . \$TimeOut . \".\ + \");\r\ + \n /ip/firewall/address-list/set \$Entry timeout=\$TimeOut;\r\ + \n :set (\$IPv4Addresses->\$Branch->\$Address);\r\ + \n :set CntRenew (\$CntRenew + 1);\r\ + \n } else={\r\ + \n :if (\$Failure = false) do={\r\ + \n :log warning (\$ScriptName . \" Removing IPv4 address \" . \$A\ + ddress . \\\r\ + \n \" from list '\" . \$ListName . \".\");\r\ + \n /ip/firewall/address-list/remove \$Entry;\r\ + \n :set CntRemove (\$CntRemove + 1);\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :foreach Entry in=[ /ipv6/firewall/address-list/find where \\\r\ + \n list=\$ListName comment=\$ListComment ] do={\r\ + \n :local Address [ /ipv6/firewall/address-list/get \$Entry address ]\ + ;\r\ + \n :local Branch [ \$GetBranch \$Address ];\r\ + \n :local TimeOut (\$IPv6Addresses->\$Branch->\$Address);\r\ + \n :if ([ :typeof \$TimeOut ] = \"time\") do={\r\ + \n :log warning (\$ScriptName . \" Renewing IPv6 address \" . \$Add\ + ress . \\\r\ + \n \" in list '\" . \$ListName . \"' with \" . \$TimeOut . \".\ + \");\r\ + \n /ipv6/firewall/address-list/set \$Entry timeout=\$TimeOut;\r\ + \n :set (\$IPv6Addresses->\$Branch->\$Address);\r\ + \n :set CntRenew (\$CntRenew + 1);\r\ + \n } else={\r\ + \n :if (\$Failure = false) do={\r\ + \n :log warning (\$ScriptName . \" Removing IPv6 address \" . \$A\ + ddress . \\\r\ + \n \" from list '\" . \$ListName . \".\");\r\ + \n /ipv6/firewall/address-list/remove \$Entry;\r\ + \n :set CntRemove (\$CntRemove + 1);\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :foreach BranchName,Branch in=\$IPv4Addresses do={\r\ + \n :log warning (\$ScriptName . \" Handling branch: \" . \$BranchName\ + );\r\ + \n :foreach Address,Timeout in=\$Branch do={\r\ + \n :log warning (\$ScriptName . \" Adding IPv4 address \" . \$Addre\ + ss . \\\r\ + \n \" to list '\" . \$ListName . \"' with \" . \$Timeout . \".\ + \");\r\ + \n :onerror Err {\r\ + \n /ip/firewall/address-list/add list=\$ListName comment=\$ListCo\ + mment \\\r\ + \n address=\$Address timeout=\$Timeout;\r\ + \n :set CntAdd (\$CntAdd + 1);\r\ + \n } do={\r\ + \n :log warning (\$ScriptName . \" Failed to add IPv4 address \" \ + . \$Address . \\\r\ + \n \" to list '\" . \$ListName . \"': \" . \$Err);\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :foreach BranchName,Branch in=\$IPv6Addresses do={\r\ + \n :log warning (\$ScriptName . \" Handling branch: \" . \$BranchName\ + );\r\ + \n :foreach Address,Timeout in=\$Branch do={\r\ + \n :log warning (\$ScriptName . \" Adding IPv6 address \" . \$Addre\ + ss . \\\r\ + \n \" to list '\" . \$ListName . \"' with \" . \$Timeout . \".\ + \");\r\ + \n :onerror Err {\r\ + \n /ipv6/firewall/address-list/add list=\$ListName comment=\$List\ + Comment \\\r\ + \n address=\$Address timeout=\$Timeout;\r\ + \n :set CntAdd (\$CntAdd + 1);\r\ + \n } do={\r\ + \n :log warning (\$ScriptName . \" Failed to add IPv6 address \" \ + . \$Address . \\\r\ + \n \" to list '\" . \$ListName . \"': \" . \$Err);\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :log info (\$ScriptName . \" list: \" . \$ListName . \\\r\ + \n \" (\" . [ \$HumanReadableNum (\$CntAdd + \$CntRenew) 1000 ] . \ + \")\" . \\\r\ + \n \" -- added: \" . [ \$HumanReadableNum \$CntAdd 1000 ] . \\\r\ + \n \" - renewed: \" . [ \$HumanReadableNum \$CntRenew 1000 ] . \\\r\ + \n \" - removed: \" . [ \$HumanReadableNum \$CntRemove 1000 ]);\r\ + \n }\r\ + \n} do={\r\ + \n :global ExitError; \$ExitError \$ExitOK [ :jobname ] \$Err;\r\ + \n}\r\ + \n" +add dont-require-permissions=no name=awg-env owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/\ + container/envs/add list=awg-proxy-env key=AWG_LISTEN value=\":55055\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_REMOTE value=\"109.107.17\ + 7.79:55055\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_JC value=\"9\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_JMIN value=\"105\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_JMAX value=\"920\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_S1 value=\"90\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_S2 value=\"136\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_S3 value=\"45\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_S4 value=\"27\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_H1 value=\"472437748-4724\ + 38637\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_H2 value=\"707808381-7078\ + 09250\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_H3 value=\"1609078552-160\ + 9079495\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_H4 value=\"1012329484-101\ + 2329741\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_I1 value=\"\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_I2 value=\"\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_I3 value=\"\ + \"\ + \n/container/envs/add list=awg-proxy-env key=AWG_I4 value=\"\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_I5 value=\"\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_SERVER_PUB value=\"o5IgCl\ + 1/G6xbl6j7sPaP4ZNYREeYOmqZjKbm+v0b9jU=\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_CLIENT_PUB value=[/interf\ + ace/wireguard/get [find name=wg-awg-proxy] public-key]" +add dont-require-permissions=no name=awg-container-add owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/\ + container/add file=sata1/tmp/awg-proxy-amd64.tar.gz interface=veth-awg-pro\ + xy envlist=awg-proxy-env \\\ + \n hostname=awg-proxy root-dir=sata1/awg-proxy logging=yes shm-size=4M \ + start-on-boot=yes\ + \n/container/start [find where tag~\"awg-proxy\"]" +add dont-require-permissions=yes name=awg2-webui owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":\ + put \"Download image, and save him to usb1/tmp\"\ + \n/tool/fetch url=\"https://ros.vados.ru/docker/awg2-webui-chr.tar\" dst-p\ + ath=sata1/tmp/awg2-webui.tar\ + \n\ + \n:put \"1. Create veth interface\"\ + \n/interface veth add name=veth-awg address=10.9.9.2/24 gateway=10.9.9.1\ + \n\ + \n:put \"2. Create bridge and add veth\"\ + \n/interface bridge add name=br-awg\ + \n/interface bridge port add bridge=br-awg interface=veth-awg\ + \n\ + \n:put \"3. Assign IP to bridge\"\ + \n/ip address add address=10.9.9.1/24 interface=br-awg\ + \n\ + \n:put \"4. NAT: masquerade container traffic\"\ + \n/ip firewall nat add chain=srcnat src-address=10.9.9.0/24 action=masquer\ + ade\ + \n\ + \n:put \"5. Port forward: UDP 443 \? container (AmneziaWG QUIC mimicry)\"\ + \n/ip firewall nat add chain=dstnat protocol=udp dst-port=443 action=dst-n\ + at to-addresses=10.9.9.2 to-ports=55055 in-interface=ether1\ + \n\ + \n:put \"6. Port forward: HTTPS 9443 \? container Web UI\"\ + \n/ip firewall nat add chain=dstnat protocol=tcp dst-port=9443 action=dst-\ + nat to-addresses=10.9.9.2 to-ports=443 in-interface=ether1\ + \n\ + \n:put \"Create environment list..\"\ + \n/container envs add list=awgui-env key=WEB_USER value=vados\ + \n/container envs add list=awgui-env key=WEB_PASS value=zoomzoomrulezz\ + \n/container envs add list=awgui-env key=SECRET_KEY value=RanDomSecret32Sy\ + mboLs2325272931q\ + \n/container envs add list=awgui-env key=AWG_ENDPOINT value=10.30.30.10:44\ + 3\ + \n\ + \n:put \"Load and start container...\"\ + \n/container add file=sata1/tmp/awg2-webui.tar interface=veth-awg hostname\ + =awg2-webui \\\ + \nroot-dir=sata1/awg2-webui layer-dir=sata1/docker/awg2-webu/ envlist=awgu\ + i-env start-on-boot=yes logging=yes shm-size=4M\ + \n\ + \n:put \"Start...\"\ + \n/container start 0\ + \n\ + \n:put \"Verify...\"\ + \n/container print\ + \n\ + \n:put \"Should show: status=running\"\ + \n\ + \n/log print where topics~\"container\"\ + \n\ + \n:put \"Should show: AWG started, nginx running\"\ + \n" +add dont-require-permissions=no name=awg-proxy-go-Client owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="\ + \n:put \"[::::::::::::::: START :::::::::::::::]\"\ + \n:put \"\"\ + \n:put \"Add ram disk tmpfs\"\ + \n#/disk add type=tmpfs tmpfs-max-size=100M slot=ram\ + \n\ + \n:put \"veth and network:\"\ + \n /interface/bridge add name=docker port-cost-mode=short\ + \n /ip/address/add address=192.168.100.1/24 interface=docker\ + \n\ + \n /interface/veth add address=192.168.100.3/24 gateway=192.168.100.1 name\ + =awg-proxy-go-client\ + \n /interface/bridge/port add bridge=docker interface=awg-proxy-go-client\ + \n\ + \n:put \"add config-client.json\"\ + \n/file/add name=sata1/mounts/awg-proxy-go/config-client.json\ + \n/container/mounts/add src=sata1/mounts/awg-proxy-go/config-client.json d\ + st=/app/config.json list=awgcfg-client\ + \n\ + \n#:put \"Download image, and save him to sata1/tmp/awgproxy-0.0.1-0034-am\ + d64.tar.gz\"\ + \n#/tool/fetch url=\"https://ros.vados.ru/amneziawg/img/awgproxy-0.0.1-003\ + 4-amd64.tar.gz\" dst-#path=\"tmp/awgproxy-0.0.1-0034-amd64.tar.gz\"\ + \n\ + \n:put \"Add container\"\ + \n/container/add file=\"usb1/dst/awgproxy-0.0.1-0036-amd64.tar\" interface\ + =awg-proxy-go-client hostname=awg-proxy-go-client root-dir=sata1/awg-proxy\ + -client logging=yes start-on-boot=yes workdir=sata1/docker/ name=\"awg-pro\ + xy-go-client\" mountlists=awgcfg-client\ + \n\ + \n:put \"Start container\"\ + \n#/container/start awg-proxy-go-client\ + \n\ + \n:put \"Add user:\"\ + \n /user/add address=192.168.100.2 group=write name=awg-proxy-go password=\ + peskar55\ + \n\ + \n:put \"Add forwarding port web-ui\"\ + \n /ip firewall nat add action=dst-nat chain=dstnat dst-port=8089 in-inter\ + face=ether1 protocol=tcp to-addresses=192.168.100.3 to-port=8088\ + \n\ + \n:put \"Add native WireGuard-server wg-awg, add address.\"\ + \n /interface/wireguard/add name=wg-awg-client\ + \n /ip/address/add address=172.17.0.1/24 interface=wg-awg-client\ + \n\ + \n:put \"\"\ + \n:put \"[::::::::::::::: END :::::::::::::::]\"\ + \n" +add comment=awg2wg dont-require-permissions=no name=awg2wg-uninstall owner=\ + Manager policy=ftp,read,write,policy source="\r\ + \n :put \"Uninstalling AmneziaWG...\"\r\ + \n :log info \"Uninstalling AmneziaWG...\"\r\ + \n /ip/route/remove [find where comment=awg2wg-tunnel]\r\ + \n :do { /tool/netwatch/remove [find where comment=Monitor-AWG2WG] } on-e\ + rror={}\r\ + \n :do { /ip/route/remove [find where comment=Monitor-AWG2WG] } on-error=\ + {}\r\ + \n /container/stop [find where interface=veth-awg2wg]\r\ + \n :delay 7s\r\ + \n /interface/list/member/remove [find where interface=wg-awg2wg]\r\ + \n /container/remove [find where interface=veth-awg2wg]\r\ + \n /container/envs/remove [find where list=\"awg2wg-env\"]\r\ + \n /ip/firewall/nat/remove [find where out-interface=\"wg-awg2wg\"]\r\ + \n /ip/address/remove [find where address=\"172.20.0.1/30\"]\r\ + \n /interface/veth/remove [find where name=\"veth-awg2wg\"]\r\ + \n /interface/wireguard/peers/remove [find where interface=\"wg-awg2wg\"]\ + \r\ + \n /ip/address/remove [find where interface=\"wg-awg2wg\"]\r\ + \n /interface/wireguard/remove [find where name=\"wg-awg2wg\"]\r\ + \n :do { /file/remove [find where name~\"awg2wg.+tar\"] } on-error={}\r\ + \n :do { /file/remove [find where name=\"disk1/awg2wg\"] } on-error={}\r\ + \n :put \"Uninstall AmneziaWG Proxy complete!\"\r\ + \n :log info \"Uninstall AmneziaWG Proxy complete!\"\r\ + \n /system/script/remove [find where name=awg2wg-uninstall]\r\ + \n" +add dont-require-permissions=no name=amneziawg-mikrotik-c owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="\ + \n:global GlobalScriptsUrl \"https://ros.vados.ru/\";\ + \n:global imagesPath \"amneziawg/img/\";\ + \n:local Image \"awg-proxy-amd64-7.20-Docker.tar.gz\";\ + \n\ + \n:global DstDir \"sata1/dst/\";\ + \n\ + \n:local Url=(\$GlobalScriptsUrl . \$ImagesPath . \$Image);\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n" +add comment=Generator dont-require-permissions=no name=awg-VDSina owner=vados \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source="# ============================================================\ + \n# AWG Proxy -- MikroTik RouterOS (protocol: v2)\ + \n# 2026-06-30 22:08:12\ + \n# ============================================================\ + \n\ + \n# 0. Check prerequisites\ + \n:if ([:len [/system/package/find where name=\"container\" disabled=no]] \ + = 0) do={\ + \n :put \"Container package is not installed or disabled. Install it and \ + reboot.\"\ + \n :error \"Usage: /system/device-mode/update container=yes\"\ + \n}\ + \n\ + \n# Auto-detect storage\ + \n:global awgDisk \"disk1\"\ + \n{\ + \n :local usbFree 0\ + \n :do { :set usbFree [/disk/get [find where mount-point=\"usb1\"] free] \ + } on-error={}\ + \n :if (\$usbFree > 0) do={\ + \n :set awgDisk \"usb1\"\ + \n :put (\"Using USB: usb1 (\" . (\$usbFree / 1048576) . \" MB free)\")\ + \n } else={\ + \n :if ([:len [/disk/find where slot=\"usb1\"]] > 0) do={\ + \n :put \"USB found but not mounted. Format as ext4\?\"\ + \n :put \"Press 'y' to confirm:\"\ + \n :local key [/terminal/inkey timeout=30]\ + \n :if (\$key = 121 || \$key = 89) do={\ + \n /disk/format-drive usb1 file-system=ext4 label=usb1\ + \n :delay 3s\ + \n :do { :set usbFree [/disk/get [find where mount-point=\"usb1\"] \ + free] } on-error={}\ + \n :if (\$usbFree > 0) do={\ + \n :set awgDisk \"usb1\"\ + \n } else={ :put \"Format failed, using disk1\" }\ + \n } else={ :put \"Using disk1\" }\ + \n } else={ :put \"No USB, using disk1\" }\ + \n }\ + \n}\ + \n\ + \n# 1. Uninstall script\ + \n/system/script/add name=awg-VDSina-uninstall comment=awg-VDSina source={\ + \n :put \"Uninstallimg AmneziaWG...\"\ + \n :log info \"Uninstallimg AmneziaWG...\"\ + \n :local prevDNS \"\"\ + \n :do { :set prevDNS [/container/envs/get [find where list=\"awg-VDSina-\ + env\" key=\"AWG_PREV_DNS\"] value] } on-error={}\ + \n :local storageDisk \"disk1\"\ + \n :do { :set storageDisk [/container/envs/get [find where list=\"awg-VDS\ + ina-env\" key=\"AWG_STORAGE\"] value] } on-error={}\ + \n :local prevTmpdir \"\"\ + \n :if (\$storageDisk != \"disk1\") do={\ + \n :do { :set prevTmpdir [/container/envs/get [find where list=\"awg-VD\ + Sina-env\" key=\"AWG_PREV_TMPDIR\"] value] } on-error={}\ + \n }\ + \n /ip/route/remove [find where comment=awg-VDSina-tunnel]\ + \n /ip/route/remove [find where comment=awg-VDSina-dns]\ + \n /container/stop [find where interface=veth-awg-VDSina]\ + \n :delay 7s\ + \n /container/remove [find where interface=veth-awg-VDSina]\ + \n /container/envs/remove [find where list=\"awg-VDSina-env\"]\ + \n :if (\$prevDNS != \"\") do={\ + \n :do { /ip/dns/set servers=\"\$prevDNS\" } on-error={}\ + \n :put (\"DNS restored to: \$prevDNS\")\ + \n }\ + \n :if (\$storageDisk != \"disk1\") do={\ + \n /container/config set tmpdir=\$prevTmpdir\ + \n :put (\"Container tmpdir restored to: \$prevTmpdir\")\ + \n }\ + \n /ip/firewall/nat/remove [find where out-interface=\"wg-awg-VDSina\"]\ + \n /ip/firewall/nat/remove [find where src-address=\"172.19.75.208/30\"]\ + \n /ip/address/remove [find where address=\"172.19.75.209/30\"]\ + \n /interface/veth/remove [find where name=\"veth-awg-VDSina\"]\ + \n /interface/wireguard/peers/remove [find where interface=\"wg-awg-VDSin\ + a\"]\ + \n /ip/address/remove [find where interface=\"wg-awg-VDSina\"]\ + \n /interface/wireguard/remove [find where name=\"wg-awg-VDSina\"]\ + \n :do { /file/remove [find where name~\"awg-VDSina.+tar\"] } on-error={}\ + \n :do { /file/remove [find where name=(\$storageDisk . \"/awg-VDSina\")]\ + \_} on-error={}\ + \n :do { /file/remove [find where name=(\$storageDisk . \"/ru_ranges_time\ + out.rsc\")] } on-error={}\ + \n :if (\$storageDisk != \"disk1\") do={\ + \n :do { /file/remove [find where name~(\$storageDisk . \"/pull\")] } o\ + n-error={}\ + \n }\ + \n :put \"Uninstall AmneziaWG Proxy complete!\"\ + \n :log info \"Uninstall AmneziaWG Proxy complete!\"\ + \n /system/script/remove [find where name=awg-VDSina-uninstall]\ + \n}\ + \n\ + \n# 2. Network infrastructure\ + \n/interface/veth/add name=veth-awg-VDSina address=172.19.75.210/30 gatewa\ + y=172.19.75.209\ + \n/ip/address/add address=172.19.75.209/30 interface=veth-awg-VDSina\ + \n/ip/firewall/nat/add chain=srcnat action=masquerade src-address=172.19.7\ + 5.208/30\ + \n\ + \n# 3. WireGuard interface (MikroTik derives the public key automatically)\ + \n/interface/wireguard/add name=wg-awg-VDSina private-key=\"4CHyNhbOs923/E\ + yNDguMXJXdghL2I3UyzXPwrANVgHs=\" listen-port=14710 disabled=yes\ + \n/interface/wireguard/peers/add interface=wg-awg-VDSina public-key=\"o5Ig\ + Cl1/G6xbl6j7sPaP4ZNYREeYOmqZjKbm+v0b9jU=\" preshared-key=\"RNvF7T1cg5tyXcY\ + QnD9N5BEp6e20vVJQTv0GMjyvHU0=\" endpoint-address=172.19.75.210 endpoint-po\ + rt=51820 allowed-address=0.0.0.0/0 persistent-keepalive=10\ + \n/ip/address/add address=10.9.0.3/32 interface=wg-awg-VDSina\ + \n\ + \n# 4. Container environment variables\ + \n/container/envs/add list=awg-VDSina-env key=AWG_LISTEN value=\":51820\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_REMOTE value=\"109.107.1\ + 77.79:55055\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_JC value=\"9\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_JMIN value=\"105\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_JMAX value=\"920\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_S1 value=\"90\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_S2 value=\"136\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_H1 value=\"472437748-472\ + 438637\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_H2 value=\"707808381-707\ + 809250\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_H3 value=\"1609078552-16\ + 09079495\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_H4 value=\"1012329484-10\ + 12329741\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_SERVER_PUB value=\"o5IgC\ + l1/G6xbl6j7sPaP4ZNYREeYOmqZjKbm+v0b9jU=\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_CLIENT_PUB value=[/inter\ + face/wireguard/get [find name=wg-awg-VDSina] public-key]\ + \n/container/envs/add list=awg-VDSina-env key=AWG_S3 value=\"45\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_S4 value=\"27\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_I1 value=\"\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_I2 value=\"\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_I3 value=\"\ + \"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_I4 value=\"\"\ + \n/container/envs/add list=awg-VDSina-env key=AWG_I5 value=\"\"\ + \n\ + \n# 5. DNS\ + \n{ :local rawDNS [/ip/dns/get servers]; :local prevDNS \"\"; :foreach s i\ + n=[:toarray \$rawDNS] do={ :if ([:len \$prevDNS] > 0) do={ :set prevDNS \"\ + \$prevDNS,\$s\" } else={ :set prevDNS \$s } }; /container/envs/add list=aw\ + g-VDSina-env key=AWG_PREV_DNS value=\"\$prevDNS\" }\ + \n# NOTE: replaces existing DNS configuration\ + \n/ip/dns/set servers=109.107.177.79\ + \n/ip/route/add dst-address=109.107.177.79/32 gateway=wg-awg-VDSina distan\ + ce=1 comment=awg-VDSina-dns\ + \n\ + \n# 6. Download, create and start container\ + \n/container/envs/add list=awg-VDSina-env key=AWG_STORAGE value=\$awgDisk\ + \n:if (\$awgDisk != \"disk1\") do={\ + \n :local tmpPath (\$awgDisk . \"/pull\")\ + \n /container/envs/add list=awg-VDSina-env key=AWG_PREV_TMPDIR value=[/co\ + ntainer/config/get tmpdir]\ + \n /container/config set tmpdir=\$tmpPath memory-high=200M\ + \n}\ + \n{\ + \n :local arch [/system/resource/get architecture-name]\ + \n :local ver [/system/resource/get version]\ + \n :local dotPos [:find \$ver \".\"]\ + \n :local rest [:pick \$ver (\$dotPos + 1) [:len \$ver]]\ + \n :local endPos [:find \$rest \".\"]\ + \n :if ([:typeof \$endPos] = \"nil\") do={ :set endPos [:find \$rest \" \ + \"] }\ + \n :local minor [:tonum [:pick \$rest 0 \$endPos]]\ + \n :local suffix \"\"\ + \n :if (\$minor <= 20) do={ :set suffix \"-7.20-Docker\" }\ + \n :local file \"\"\ + \n :if (\$arch = \"arm64\") do={ :set file (\"awg-proxy-arm64\" . \$suffi\ + x . \".tar.gz\") }\ + \n :if (\$arch = \"arm\") do={ :set file (\"awg-proxy-arm\" . \$suffix . \ + \".tar.gz\") }\ + \n :if (\$arch ~ \"x86\") do={ :set file (\"awg-proxy-amd64\" . \$suffix \ + . \".tar.gz\") }\ + \n :if (\$file = \"\") do={ :error \"Unsupported architecture: \$arch\" }\ + \n :local url \"https://github.com/timbrs/amneziawg-mikrotik-c/releases/l\ + atest/download/\$file\"\ + \n :local filePath \$file\ + \n :if (\$awgDisk != \"disk1\") do={\ + \n :set filePath (\$awgDisk . \"/pull/\" . \$file)\ + \n :do { /file/make-directory (\$awgDisk . \"/pull\") } on-error={}\ + \n }\ + \n :if ([:len [/file/find where name=\$filePath]] = 0) do={\ + \n :local freeStorage 0\ + \n :if (\$awgDisk = \"disk1\") do={\ + \n :set freeStorage [/system/resource/get free-hdd-space]\ + \n } else={\ + \n :do { :set freeStorage [/disk/get [find where mount-point=\$awgDis\ + k] free] } on-error={}\ + \n }\ + \n :if (\$freeStorage < 262144) do={\ + \n :put (\"WARNING: Low disk space (\" . (\$freeStorage / 1024) . \"K\ + B free). Need at least 256KB.\")\ + \n :put \"See: https://github.com/timbrs/amneziawg-mikrotik-c#insuffi\ + cient-disk-space\"\ + \n :error \"Insufficient disk space\"\ + \n }\ + \n :put (\"Fetching: \$url -> \" . \$filePath)\ + \n /tool/fetch url=\$url dst-path=\$filePath http-max-redirect-count=10\ + \n } else={\ + \n :put (\"File already exists: \" . \$filePath)\ + \n }\ + \n :local rootDir (\$awgDisk . \"/awg-VDSina\")\ + \n /container/add file=\$filePath interface=veth-awg-VDSina envlist=awg-V\ + DSina-env hostname=awg-VDSina root-dir=\$rootDir logging=no start-on-boot=\ + yes comment=awg-VDSina\ + \n :if (\$minor > 20) do={ [:parse \"/container/set [find where interface\ + =veth-awg-VDSina] shm-size=4M\"] }\ + \n /file/remove \$filePath\ + \n :local freeMem [/system/resource/get free-memory]\ + \n :if (\$freeMem < 16777216) do={\ + \n :put (\"WARNING: Low memory (\" . (\$freeMem / 1048576) . \"MB free)\ + . 16MB+ recommended.\")\ + \n }\ + \n /container/start [find where interface=veth-awg-VDSina]\ + \n :do { /file/remove [find where name=\"console-dump.txt\"] } on-error={\ + }\ + \n :put \"Waiting for container to start...\"\ + \n :delay 5s\ + \n /interface/wireguard/enable wg-awg-VDSina\ + \n :put \"WireGuard interface enabled\"\ + \n :put \"Installation complete!\"\ + \n :put \"Ping test: 109.107.177.79 via AmneziaWG tunnel. repeat 10 times\ + \"\ + \n /ping 109.107.177.79 count=10 interval=1\ + \n :put \"\"\ + \n :put \"======= NEXT STEPS =======\"\ + \n :put \"\"\ + \n :put \"Route specific traffic through the AmneziaWG tunnel:\"\ + \n :put \"\"\ + \n :put \" Route a single host:\"\ + \n :put \" /ip/route/add dst-address=8.8.8.8/32 gateway=wg-awg-VDSina \ + comment=awg-VDSina-tunnel\"\ + \n :put \"\"\ + \n :put \" Route a subnet:\"\ + \n :put \" /ip/route/add dst-address=100.0.0.0/8 gateway=wg-awg-VDSina\ + \_comment=awg-VDSina-tunnel\"\ + \n :put \"\"\ + \n :put \" Remove a route:\"\ + \n :put \" /ip/route/remove [find where comment=awg-VDSina-tunnel dst-\ + address=8.8.8.8/32]\"\ + \n :put \"\"\ + \n :put \" Show all tunnel routes:\"\ + \n :put \" /ip/route/print where comment=awg-VDSina-tunnel\"\ + \n :put \"\"\ + \n :put \" Uninstall:\"\ + \n :put \" /system/script/run awg-VDSina-uninstall\"\ + \n :put \"=========================\"\ + \n}\ + \n\ + \n# 7. Verification\ + \n# /container/print\ + \n# /interface/wireguard/print\ + \n# /interface/wireguard/peers/print\ + \n" +add comment="wg2awg GH repo in client mode" dont-require-permissions=no name=\ + "wg2awg client" owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="\ + \n:local Arch \"amd64\";\ + \n:local Version \"v0.0.1\";\ + \n:local Name wg2awg;\ + \n:local WorkDisk \"sata1\";\ + \n:local WorkDir (\$WorkDisk . \"/\" . \$Name);\ + \n:local Bridge (\"Bridge-\" . \$Name);\ + \n:local VetchMTU \"1280\";\ + \n:local Vetch (\"vetch-\" . \$Name);\ + \n:local VetchNet \"10.9.0.0\";\ + \n:local VetchGate \"10.9.0.1\";\ + \n:local VetchAddr \"10.9.0.3/32\";\ + \n:local Env (\$Name . \"-env\");\ + \n:local ConfDir (\"/etc/\" . \$Name . \"/\");\ + \n:local ConfFile (\$Name . \".conf\");\ + \n:local Mounts (\$WorkDisk . \"/mounts/\" . \$Name . \"/\");\ + \n:local RootDir (\$WorkDisk . \"/\" . \$Name);\ + \n:local Registry \"https://registry-1.docker.io\";\ + \n:local FetchFile (\$Name . \"-\" . \$Arch . \"-oci.tar\");\ + \n:local FetchUrl (\"https://github.com/vados-dev/wg2awg/releases/download\ + /\" . \$Version . \"/\" . \$FetchFile);\ + \n:local FetchDir (\$WorkDisk . \"/dst/\");\ + \n:local AwgListen \":44044\";\ + \n:local AwgRemote \"109.107.177.79\";\ + \n\ + \n#:put \"\"\ + \n#put \"Add ram disk tmpfs\"\ + \n#/disk add type=tmpfs tmpfs-max-size=100M slot=ram\ + \n\ + \n\ + \n:log warning \"[::::::::::::::: START :::::::::::::::]\"\ + \n:log warning \"Add veth and nat...\";\ + \n\ + \n/interface/veth/add name=\$Vetch address=\$VetchAddr gateway=\$VetchGate\ + ;\ + \n\ + \n:do {\ + \n:log warning (\"Remove interface: name=\" . \$Vetch . \", address=\" . \ + \$VetchAddr . \", gateway=\" . \$VetchGate);\ + \n/interface/veth/remove [find name=\$Vetch];\ + \n:log warning (\"Add interface: name=\" . \$Vetch . \", address=\" . \$Ve\ + tchAddr . \", gateway=\" . \$VetchGate);\ + \n/interface/veth/add name=\$Vetch address=\$VetchAddr gateway=\$VetchGate\ + ;\ + \n:log warning (\"Remove ip address: address=\" . \$VetchGate . \", interf\ + ace=\" . \$Vetch);\ + \n/ip/address/remove [find address=\$VetchGate interface=\$Vetch];\ + \n:log warning (\"Add ip address: address=\" . \$VetchGate . \", interface\ + =\" . \$Vetch);\ + \n/ip/address/add address=\$VetchGate interface=\$Vetch;\ + \n:log warning (\"Remove firewall nat rule: chain=srcnat action=masquerade\ + \_src-address=\" . \$VetchNet . \"/24\");\ + \n/ip/firewall/nat/remove [find chain=srcnat action=masquerade src-address\ + =(\$VetchNet . \"/24\")];\ + \n:log warning (\"Add firewall nat rule: chain=srcnat action=masquerade sr\ + c-address=\" . \$VetchNet . \"/24\");\ + \n/ip/firewall/nat/add chain=srcnat action=masquerade src-address=(\$VethN\ + et . \"/24\");\ + \n} on-error={}\ + \n\ + \n#:do {\ + \n#/file add type=directory name=\$Mounts\ + \n#} on-error={}\ + \n\ + \n\ + \n:do {\ + \n:log warning (\"Remove WorkDir: \" . \$WorkDir . \"...\");\ + \n/file remove [find name=\$WorkDir];\ + \n:log warning (\"Add WorkDir: \" . \$WorkDir . \"...\");\ + \n/file add type=directory name=\$WorkDir;\ + \n:log warning (\"Remove config file \" . \$Mounts . \$ConfFile . \"...\")\ + ;\ + \n/file remove [find name=(\$Mounts . \$ConfFile)];\ + \n:log warning (\"Add config file \" . \$Mounts . \$ConfFile . \"...\");\ + \n/file add type=file name=(\$Mounts . \$ConfFile) contents=\"[Interface]\ + \nPrivateKey = 4CHyNhbOs923/EyNDguMXJXdghL2I3UyzXPwrANVgHs=\ + \nAddress = \$VetchAddr\ + \nMTU = \$VetchMTU\ + \nDNS = 109.107.177.79\ + \nPreUp = ip route add 109.107.177.79/32 via \$VetchGate dev \$Vetch\ + \nPostDown = ip route del 109.107.177.79/32 via \$VetchGate dev \$Vetch\ + \nJc = 9\ + \nJmin = 105\ + \nJmax = 920\ + \nS1 = 90\ + \nS2 = 136\ + \nS3 = 45\ + \nS4 = 27\ + \nH1 = 472437748-472438637\ + \nH2 = 707808381-707809250\ + \nH3 = 1609078552-1609079495\ + \nH4 = 1012329484-1012329741\ + \nI1 = <\ + r 4>\ + \nI2 = \ + \nI3 = \ + \nI4 = \ + \nI5 = \ + \n\ + \n[Peer]\ + \nPublicKey = o5IgCl1/G6xbl6j7sPaP4ZNYREeYOmqZjKbm+v0b9jU=\ + \nAllowedIPs = 0.0.0.0/0\ + \nEndpoint = \$AwgRemote\ + \nPersistentKeepalive = 10\ + \nPresharedKey = RNvF7T1cg5tyXcYQnD9N5BEp6e20vVJQTv0GMjyvHU0=\";\ + \n} on-error={}\ + \n\ + \n\ + \n:do {\ + \n:log warning (\"Remove mounts list \$Env...\");\ + \n/container/mounts/remove [find list=\$Env];\ + \n:log warning (\"Add mounts list \$Env...\");\ + \n/container/mounts/add src=(\$Mounts . \$ConfFile) dst=(\$ConfDir . \$Con\ + fFile) list=\$Env mode=ro;\ + \n:log warning (\"Remove Environment keys.\");\ + \n/container/envs/remove [find list=\$Env];\ + \n:log warning (\"Add Environment keys.\");\ + \n/container/envs/add list=\$Env key=AWG_CONFIG value=(\$ConfDir . \$ConfF\ + ile);\ + \n/container/envs/add list=\$Env key=AWG_LISTEN value=\$AwgListen\ + \n/container/envs/add list=\$Env key=AWG_REMOTE value=\$AwgRemote\ + \n/container/envs/add list=\$Env key=AWG_MODE value=client\ + \n} on-error={}\ + \n\ + \n:do {\ + \n:log warning (\"Remove \" . \$WorkDisk . \"/tmp\");\ + \n/file remove [find name=(\$WorkDisk . \"/tmp\")];\ + \n:log warning (\"Add \" . \$WorkDisk . \"/tmp\");\ + \n/file add type=directory name=(\$WorkDisk . \"/tmp\");\ + \n:log warning (\"Container add tmpdir \" . \$WorkDisk . \"/tmp\");\ + \n/container config set tmpdir=(\$WorkDisk . \"/tmp\");\ + \n} on-error={}\ + \n\ + \n:do {\ + \n:log warning (\"Remove \" . \$FetchDir . \$FetchFile . \".\");\ + \n/file remove [find name=(\$FetchDir . \$FetchFile)];\ + \n:log warning (\"Download image from \" . \$FetchUrl . \" and save him to\ + \_\" . \$FetchDir . \$FetchFile . \".\");\ + \n/tool/fetch url=\$FetchUrl dst-path=(\$FetchDir . \$FetchFile);\ + \n} on-error={}\ + \n\ + \n#:log warning (\"Container set registry \" . \$Registry . \" tmpdir= \" \ + . \$WorkDisk . \"/tmp\");\ + \n#/container/config/set registry-url=\$Registry tmpdir=(\$WorkDisk . \"/t\ + mp\");\ + \n\ + \n#:do {\ + \n:log warning (\"Container add width: file=\" . \$FetchDir . \$FetchFile \ + . \", interface=\" . \$Vetch . \", hostname=\" . \$Name . \", mountlists=\ + \" . \$Env . \", envlist=\" . \$Env . \", root-dir=\" . \$RootDir . \", wo\ + rkdir=\" . \$WorkDir . \", name=\" . \$Name . \", logging=yes, start-on-bo\ + ot=yes\");\ + \n/container/add file=(\$FetchDir . \$FetchFile) interface=\$Vetch hostnam\ + e=\$Name mountlists=\$Env envlist=\$Env root-dir=\$RootDir workdir=\$WorkD\ + ir name=\$Name logging=yes start-on-boot=yes;\ + \n#} on-error={}\ + \n\ + \n\ + \n#/container/start [find where interface=\$Vetch];\ + \n\ + \n#/interface/bridge add name=\$Bridge port-cost-mode=short;\ + \n#/ip/address/add address=192.168.100.1/24 interface=\$Bridge;\ + \n\ + \n#/interface/veth add address=192.168.100.3/24 gateway=192.168.100.1 name\ + =awg-proxy-go-client\ + \n#/interface/bridge/port add bridge=docker interface=awg-proxy-go-client\ + \n\ + \n#:put \"add config-client.json\"\ + \n#/file/add name=sata1/mounts/awg-proxy-go/config-client.json\ + \n#/container/mounts/add src=sata1/mounts/awg-proxy-go/config-client.json \ + dst=/app/config.json list=awgcfg-client\ + \n\ + \n#:put \"Download image, and save him to sata1/tmp/awgproxy-0.0.1-0034-am\ + d64.tar.gz\"\ + \n#/tool/fetch url=\"https://ros.vados.ru/amneziawg/img/awgproxy-0.0.1-003\ + 4-amd64.tar.gz\" dst-#path=\"tmp/awgproxy-0.0.1-0034-amd64.tar.gz\"\ + \n\ + \n#:put \"Add container\"\ + \n#/container/add file=\"usb1/dst/awgproxy-0.0.1-0036-amd64.tar\" interfac\ + e=awg-proxy-go-client hostname=awg-proxy-go-client root-dir=sata1/awg-prox\ + y-client logging=yes start-on-boot=yes workdir=sata1/docker/ name=\"awg-pr\ + oxy-go-client\" mountlists=awgcfg-client\ + \n\ + \n#:put \"Start container\"\ + \n#/container/start awg-proxy-go-client\ + \n\ + \n#:put \"Add user:\"\ + \n#/user/add address=192.168.100.2 group=write name=awg-proxy-go password=\ + peskar55\ + \n\ + \n#:put \"Add forwarding port web-ui\"\ + \n#/ip firewall nat add action=dst-nat chain=dstnat dst-port=8089 in-inter\ + face=ether1 protocol=tcp to-addresses=192.168.100.3 to-port=8088\ + \n\ + \n#:put \"Add native WireGuard-server wg-awg, add address.\"\ + \n#/interface/wireguard/add name=wg-awg-client\ + \n#/ip/address/add address=172.17.0.1/24 interface=wg-awg-client\ + \n\ + \n:put \"\"\ + \n:put \"[::::::::::::::: END :::::::::::::::]\"\ + \n" +add dont-require-permissions=no name=tmp_print owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/\ + interface/wireguard/add name=wg-VDSina private-key=\"4CHyNhbOs923/EyNDguMX\ + JXdghL2I3UyzXPwrANVgHs=\" listen-port=21641 \ + \n :put \"======= AWG PROXY SETUP =======\"\ + \n :put \"\"\ + \n :put \"Run awg-proxy on your external device (10.30.30.33).\"\ + \n :put \"\"\ + \n :put \"1. Download the binary for your platform:\"\ + \n :put \" Linux arm64: https://github.com/timbrs/amneziawg-mikrotik-c/\ + releases/latest/download/awg-proxy-linux-arm64\"\ + \n :put \" Linux arm: https://github.com/timbrs/amneziawg-mikrotik-c/\ + releases/latest/download/awg-proxy-linux-arm\"\ + \n :put \" Linux amd64: https://github.com/timbrs/amneziawg-mikrotik-c/\ + releases/latest/download/awg-proxy-linux-amd64\"\ + \n :put \"\"\ + \n :put \"2. Set environment variables and run:\"\ + \n :put \"export AWG_LISTEN=\\\":51820\\\"\"\ + \n :put \"export AWG_REMOTE=\\\"109.107.177.79:55055\\\"\"\ + \n :put \"export AWG_JC=\\\"9\\\"\"\ + \n :put \"export AWG_JMIN=\\\"105\\\"\"\ + \n :put \"export AWG_JMAX=\\\"920\\\"\"\ + \n :put \"export AWG_S1=\\\"90\\\"\"\ + \n :put \"export AWG_S2=\\\"136\\\"\"\ + \n :put \"export AWG_H1=\\\"472437748-472438637\\\"\"\ + \n :put \"export AWG_H2=\\\"707808381-707809250\\\"\"\ + \n :put \"export AWG_H3=\\\"1609078552-1609079495\\\"\"\ + \n :put \"export AWG_H4=\\\"1012329484-1012329741\\\"\"\ + \n :put \"export AWG_SERVER_PUB=\\\"o5IgCl1/G6xbl6j7sPaP4ZNYREeYOmqZjKbm+\ + v0b9jU=\\\"\"\ + \n :put (\"export AWG_CLIENT_PUB=\\\"\" . [/interface/wireguard/get [find\ + \_name=wg-VDSina] public-key] . \"\\\"\")\ + \n :put \"export AWG_S3=\\\"45\\\"\"\ + \n :put \"export AWG_S4=\\\"27\\\"\"\ + \n :put \"export AWG_I1=\\\"\\\"\"\ + \n :put \"export AWG_I2=\\\"\\\"\"\ + \n :put \"export AWG_I3=\\\"\\\"\"\ + \n :put \"export AWG_I4=\\\"\\\"\"\ + \n :put \"export AWG_I5=\\\"\\\"\"\ + \n :put \"\"\ + \n :put \"chmod +x awg-proxy-linux-* && ./awg-proxy-linux-\"\ + \n :put \"\"\ + \n :put \"3. (Optional) Create a systemd service for auto-start.\"\ + \n :put \"\"\ + \n :put \"Route specific traffic through the AmneziaWG tunnel:\"\ + \n :put \"\"\ + \n :put \" Route a single host:\"\ + \n :put \" /ip/route/add dst-address=8.8.8.8/32 gateway=wg-VDSina comm\ + ent=VDSina-tunnel\"\ + \n :put \"\"\ + \n :put \" Route a subnet:\"\ + \n :put \" /ip/route/add dst-address=100.0.0.0/8 gateway=wg-VDSina com\ + ment=VDSina-tunnel\"\ + \n :put \"\"\ + \n :put \" Remove a route:\"\ + \n :put \" /ip/route/remove [find where comment=VDSina-tunnel dst-addr\ + ess=8.8.8.8/32]\"\ + \n :put \"\"\ + \n :put \" Show all tunnel routes:\"\ + \n :put \" /ip/route/print where comment=VDSina-tunnel\"\ + \n :put \"\"\ + \n :put \" Uninstall:\"\ + \n :put \" /system/script/run VDSina-uninstall\"\ + \n :put \"================================\"" +/user group +add name=backup policy="telnet,ssh,ftp,read,write,policy,test,sniff,sensitive,\ + !local,!reboot,!winbox,!password,!web,!api,!romon,!rest-api" +add name=manager policy="local,ssh,ftp,read,write,policy,api,rest-api,!telnet,\ + !reboot,!test,!winbox,!password,!web,!sniff,!sensitive,!romon" +/app +set cinny firewall-redirects=8094:80:tcp:web +set goaway container-command-lines=goaway:none:docker.io/pommee/goaway:latest +set home-assistant container-command-lines=\ + home-assistant:none:lscr.io/linuxserver/homeassistant +set n8n firewall-redirects=5678:5678:tcp:web +set nextcloud container-command-lines="db:none:docker.io/postgres:17,redis:non\ + e:docker.io/valkey/valkey:/bin/sh -c 'valkey-server --port 6379 --appendon\ + ly yes --requirepass \$VALKEY_PASSWORD',server:none:docker.io/nextcloud:ap\ + ache" +set pihole environment="pihole:FTLCONF_dns_listeningMode=all,pihole:FTLCONF_we\ + bserver_api_password=password" +set redlib firewall-redirects=8087:8080:tcp:web +set solr container-command-lines=solr:none:docker.io/solr:latest +set uptime-kuma container-command-lines=\ + uptime-kuma:none:docker.io/louislam/uptime-kuma:1 +/app settings +set auto-update=yes disk=*7 lan-bridge=*3 registry-mirrors="https://reg.vados.\ + ru:5000/:https://reg.vados.ru/,https://registry-1.docker.io" +/caps-man aaa +set called-format=ssid +/caps-man access-list +add action=reject allow-signal-out-of-range=10s disabled=no interface=all \ + signal-range=-120..-80 ssid-regexp="" +add action=accept allow-signal-out-of-range=10s disabled=no interface=all \ + signal-range=-79..120 ssid-regexp="" +/caps-man configuration +add channel.band=2ghz-b/g/n country=no_country_set datapath=xs-DataPath mode=\ + ap name=xs-2G rx-chains=0,1,2,3 security=xs-Sec ssid=vados tx-chains=\ + 0,1,2,3 +add channel.band=5ghz-a/n/ac country=no_country_set datapath=xs-DataPath \ + installation=any mode=ap name=xs-5G rx-chains=0,1,2,3 security=xs-Sec \ + ssid=vados5g tx-chains=0,1,2,3 +add channel.band=2ghz-b/g/n .control-channel-width=20mhz .extension-channel=\ + XX .frequency=2422,2442,2462 .reselect-interval=20m country=belarus \ + datapath=DataPath datapath.client-to-client-forwarding=yes \ + .local-forwarding=yes hw-protection-mode=rts-cts installation=any name=2G \ + rx-chains=0,1,2,3 security=WPA2 ssid=vados tx-chains=0,1,2,3 +add channel.band=5ghz-a/n/ac .control-channel-width=20mhz .extension-channel=\ + XXXX .frequency=5180,5200,5220,5240,5260 .reselect-interval=20m \ + .skip-dfs-channels=yes country=belarus datapath=DataPath \ + datapath.client-to-client-forwarding=yes .local-forwarding=yes \ + hw-protection-mode=rts-cts installation=any name=5G security=WPA2 ssid=\ + vados5g +/caps-man datapath +add bridge=*3 client-to-client-forwarding=yes local-forwarding=yes name=\ + xs-DataPath +add bridge=*3 client-to-client-forwarding=yes local-forwarding=yes name=\ + DataPath +/caps-man manager +set ca-certificate=CAPsMAN-CA-51B25E507D07 certificate=CAPsMAN-51B25E507D07 +/caps-man manager interface +set [ find default=yes ] forbid=yes +add disabled=no interface=Bridge-LAN +/caps-man provisioning +add action=create-dynamic-enabled hw-supported-modes=g,gn,b \ + master-configuration=2G name-format=prefix-identity name-prefix=2G +add action=create-dynamic-enabled hw-supported-modes=an,ac,a \ + master-configuration=5G name-format=prefix-identity name-prefix=5G +add action=create-dynamic-enabled disabled=yes hw-supported-modes=ac,an,a \ + master-configuration=2G name-format=prefix-identity name-prefix=5ghz-ac +add action=create-dynamic-enabled disabled=yes hw-supported-modes=an \ + master-configuration=5G name-format=prefix-identity name-prefix=5ghz-an +/certificate settings +set crl-store=system +/container config +set layer-dir=/sata1/docker/layers memory-high=200.0MiB memory-max=4096.0MiB \ + registry-url=https://registry-1.docker.io tmpdir=/sata1/tmp username=\ + amsternl +/container envs +add key=AWG_CONFIG list=wg2awg-env value=/etc/wg2awg/wg2awg.conf +add key=AWG_LISTEN list=wg2awg-env value=:44044 +add key=AWG_MODE list=wg2awg-env value=client +add key=AWG_REMOTE list=wg2awg-env value=109.107.177.79 +/container mounts +add dst=/etc/wg2awg/wg2awg.conf list=wg2awg-env mode=ro src=\ + /sata1/mounts/wg2awg/wg2awg.conf +/ip smb +set enabled=no +/interface bridge port +add bridge=Bridge-LAN interface=*136 +add bridge=Bridge-LAN interface=ether1 +/ipv6 settings +set allow-fast-path=no disable-ipv6=yes disable-link-local-address=yes +/interface detect-internet +set detect-interface-list=WAN internet-interface-list=Internet \ + lan-interface-list=LAN wan-interface-list=WAN +/interface list member +add interface=ether1 list=WAN +add interface=Bridge-LAN list=LAN +/interface wifi cap +set discovery-interfaces=*DF +/interface wifi capsman +set package-path=/tmpfs/capsman-packages upgrade-policy=none +/interface wireguard peers +add allowed-address=0.0.0.0/0 client-allowed-address=::/0 comment=\ + "Peer for MikroMaster connection" endpoint-address=10.30.30.81 \ + endpoint-port=33033 interface=MASTER name=MASTER persistent-keepalive=10s \ + public-key="zuRsO+dL/j6awaBWZ/NgnHR+bCNUjRWbaHFpdzkNnw4=" +/ip address +add address=10.30.30.10/24 interface=Bridge-LAN network=10.30.30.0 +add address=10.10.0.2 interface=MASTER network=10.10.0.0 +/ip dhcp-client +add disabled=yes interface=ether1 name=client1 +/ip dhcp-server +add add-arp=yes address-pool=dhcp always-broadcast=yes bootp-support=none \ + dhcp-option-set=domain-search-set disabled=yes interface=*3 lease-script=\ + lease-script lease-time=10m name=nucDHCP server-address=10.30.30.10 +/ip dns +set allow-remote-requests=yes doh-max-concurrent-queries=100 \ + doh-max-server-connections=20 servers=10.30.30.33,109.107.177.79 \ + verify-doh-cert=yes +/ip firewall service-port +set ftp disabled=yes +set tftp disabled=yes +set sip disabled=yes +set pptp disabled=yes +set rtsp disabled=no +/ip route +add check-gateway=none disabled=no distance=1 dst-address=0.0.0.0/0 gateway=\ + 10.30.30.81 routing-table=main scope=30 target-scope=10 +/ip service +set ftp disabled=yes +set reverse-proxy disabled=yes +set ssh port=2222 +set www-ssl port=4443 +set api-ssl disabled=yes +/ip service webserver +set acme-plain=no crl-plain=no graphs-plain=no graphs-secure=no index-plain=\ + no index-secure=no rest-secure=no scep-plain=no webfig-plain=no \ + webfig-secure=no +/ip ssh +set forwarding-enabled=both publickey-authentication-options=verify-required \ + strong-crypto=yes +/ip upnp +set enabled=yes +/ip upnp interfaces +add interface=*3 type=external +add interface=*B5 type=internal +/ipv6 firewall address-list +add address=2400:cb00::/32 list=CLOUDFLARE-IP-Address +add address=2606:4700::/32 list=CLOUDFLARE-IP-Address +add address=2803:f800::/32 list=CLOUDFLARE-IP-Address +add address=2405:b500::/32 list=CLOUDFLARE-IP-Address +add address=2405:8100::/32 list=CLOUDFLARE-IP-Address +add address=2a06:98c0::/29 list=CLOUDFLARE-IP-Address +add address=2c0f:f248::/32 list=CLOUDFLARE-IP-Address +/special-login +add port=serial0 +/system clock +set time-zone-autodetect=no time-zone-name=Europe/Moscow +/system identity +set name=MikroNUC +/system note +set show-at-login=no +/system ntp client +set enabled=yes +/system ntp server +set enabled=yes manycast=yes multicast=yes +/system ntp client servers +add address=10.30.30.81 +add address=0.ru.pool.ntp.org +add address=1.ru.pool.ntp.org +add address=2.ru.pool.ntp.org +add address=3.ru.pool.ntp.org +/system package update +set check-certificate=no ip-version=ipv4 +/system scheduler +add comment="Every Day Set Global Environment Variables" disabled=yes name=\ + AM-SetGlobalEnv on-event=\ + "/system/script { run AM-GlobalConfig; run AM-GlobalFunc; }" policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-time=startup +add comment="Every Day Check For Update Scripts" disabled=yes interval=1d \ + name=AM-ScriptInstallUpdate on-event=\ + ":global ScriptInstallUpdate; \$ScriptInstallUpdate;" policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-time=startup +add comment="Every Day Check For Fake Bridges" disabled=yes interval=1d name=\ + mod/AM-IPbridge on-event="/system/script/run mod/AM-IPbridge;" policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-time=startup +add comment="Telegram Chat Bot" disabled=yes interval=30s name=mod/AM-TG-Bot \ + on-event="/system/script/run mod/AM-TG-Bot;" policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-time=startup +add comment=\ + "Every 7 Day Check For Update and Backup send notification to EMail." \ + disabled=yes interval=1w name=AM-Backup-UpdateEmail on-event=\ + "/system/script { run AM-Backup-UpdateEmail; }" policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-date=2026-06-25 start-time=03:41:27 +/tool e-mail +set certificate-verification=no from=MikroNUC port=587 \ + server=smtp.mail.me.com tls=starttls user=vadossio@icloud.com +/user aaa +set accounting=no diff --git a/MikroNUC/history/20260715/20260715094001-Diff-Config.rsc b/MikroNUC/history/20260715/20260715094001-Diff-Config.rsc new file mode 100644 index 0000000..54a1585 --- /dev/null +++ b/MikroNUC/history/20260715/20260715094001-Diff-Config.rsc @@ -0,0 +1,6 @@ +5474a5475,5476 +> /system gps +> set set-system-time=yes +5489,5490d5490 +< /system package update +< set check-certificate=no ip-version=ipv4 -- 2.52.0