We are at a good level, but avoid using "keywords" (in one point or another of routers does not matter)
for example,
name=$upsName
is better (for avoid problems, not for readability)
uname=$upsName
the same for "time", etc.
We are at a good level, but avoid using "keywords" (in one point or another of routers does not matter)
for example,
name=$upsName
is better (for avoid problems, not for readability)
uname=$upsName
the same for "time", etc.
Select the code then press on the button with </> (if you hover over it with the mouse a popup will say "Preformatted text (Ctrl+E)")
Or select and issue Ctrl+E
Should put three backticks before and three backticks after.
RoS syntax should become coloured and suitable font.
Three bacticks+"routeros" should force the mode, and then you need three backticks to close.
But as rextended pointed out the parser is not fully working.
# Test #
```routeros
:put [system identity get name]
```
:put [system identity get name]
ah got it!
Is there anyway to change the date format to M-D-YY or even Nov 19 2025?
Not directly. There are no date formatting options. So you can use a custom function to build it from the ISO datetime components. Perhaps @rextended already has one that does it, but involves using :picks and an array to lookup the month names before formatting. This actually be a good example of using a local function ;).
Thank you for the answer. I’ll research it!
You guys have help tremendously!
For my opinon, the only valid date format is the ISO format 2025-11-19 because is the only format that are sortable and easily readable without any doubt about something like 12/10/25...
is the 12 October 2025?
is the 10 December 2025?
is 25 October 2012?
etc.
Read this... repository of snippets...
Since you're learning and coming from Bash... One general tip for scripting is RouterOS variables have a type. So if coming from bash or other UNIX shells, not everything is a string. Often the types help, like you can do math on time types directly (unlike bash) or add 1 to an IP address type. But this also means you may need a :typeof or the various totime,toarray,etc – not here per se, just something to be aware of. RouterOS often "casts" types to string, but not always, and often that where there is a learning curve.
And there is no "date" type, only "time" (which milliseconds since 1/1/1970) – so MikroTik's type model does have some holes since ISO date format is actually just a string
I’ll look into this, but I may stick with the ISO format due to the reasons you listed above.
Another question… does indentation matter in RouterOS script, or is it preferred more for readability?
the 2nd
@rextended Finally finished adding the email function and buttoning things up. Could you give it a once over and let know if you see any apparent issues?
I’ve tested most functions individually, but will do a full test simulating a power outage tomorrow.
# [Variables]
:global offlineTime ""
:local threshold 00:05:00
:local srcAddr 192.168.99.1
:local server "pve-node1"
:local ifaceArray [:toarray "ether7,ether6,ether5,ether4,ether3,ether2"]
:local mtikArray [:toarray "hap2,hap1,rb2,crs2,crs1"]
:local srvDomain "srv.domain.local"
:local mgmtDomain "mgmt.domain.local"
:local emailAddr "user@example.tld"
:local stage ""
:local linuxCmd "sudo shutdown now"
:local mtikCmd "/system shutdown"
:local srvPing [/ping src-address=$srcAddr address="$server.$srvDomain" count=1]
:local idName [/system identity get name]
/system ups
:local upsName [get ([find]->0) name]
:local upsMon [monitor $upsName once as-value]
:local upsOnline ($upsMon->"on-line")
:local upsRTLeft ($upsMon->"runtime-left")
:local upsLoad ($upsMon->"load")
/system clock
:local cDate [get date]
:local cTime [get time]
:local cTZone [get time-zone-name]
# [Functions]
:local funcLogShutdown do={
:local host $1
:local stage $2
:local threshold $3
:local upsRTLeft $4
:local cDate $5
:local cTime $6
:local cTZone $7
:log warning "A/C power interrupted - UPS is on auxiliary power\
\n - UPS runtime threshold ($threshold) has been reached"
:log info " - UPS runtime remaining: $upsRTLeft\
\n - Stage $stage: Initiating $host shutdown on $cDate at $cTime ($cTZone)"
}
:local funcShutdownEmail do={
:local emailAddr $1
:local stage $2
:local threshold $3
:local upsRTLeft $4
:local upsLoad $5
:local cDate $6
:local cTime $7
:local cTZone $8
:tool e-mail send to=$emailAddr\
subject="UPS ALERT: Auto-shutdown - $device"\
body="UPS is on auxiliary power.\
\n\nUPS runtime threshold has been reached.\
\n\nAuto-shutdown - Stage $stage:\
\nIncremental shutdown of $device and running services has been initiated.\
\n\n\nUPS Information:\
\n---------------------\
\nRuntime threshold: $threshold\
\nRuntime remaining: $upsRTLeft\
\nCurrent load: $upsLoad %\
\n\n\nDate: $cDate\
\nTime: $cTime ($cTZone)"
}
:local funcShutdown do={
:local srcAddr $1
:local host $2
:local domain $3
:local cmd $4
/system ssh-exec src-address=$srcAddr address="$host.$domain" command=$cmd
}
:local funcNodeIfaceMonitor do={
:local ifaceArray $1
:local nodeArray [:toarray ""]
:foreach iface in=$ifaceArray do={
:local ifaceRunning [/interface get [find name~"$iface"] value-name=running]
:local ifaceName [/interface get [find name~"$iface"] value-name=name]
:local underscorePos [:find $ifaceName "_"]
:if ($underscorePos != nil) do={
:set ifaceName [:pick $ifaceName ($underscorePos + 1) [:len $ifaceName]]
}
:if ($ifaceRunning = true) do={
:set nodeArray ($nodeArray, $ifaceName)
}
}
:return $nodeArray
}
# [Variables to call functions]
:local nodeArray [$funcNodeIfaceMonitor $ifaceArray]
# [Code]
:if ($upsOnline = true) do={
:set offlineTime ""
} else {
:if ($offlineTime = "") do={
:set offlineTime $cTime
}
:if ($upsRTLeft <= $threshold) do={
:if ($srvPing > 0) do={
:set stage "1"
$funcLogShutdown $server $stage $threshold $upsRTLeft $cDate $cTime $cTZone
$funcShutdownEmail $emailAddr $stage $threshold $upsRTLeft $upsLoad $cDate $cTime $cTZone device="PVE-Node1"
$funcShutdown $srcAddr $server $srvDomain $linuxCmd
} else={
:if ($nodeArray != "") do={
:set stage "2"
$funcShutdownEmail $emailAddr $stage $threshold $upsRTLeft $upsLoad $cDate $cTime $cTZone device="RKE2 cluster"
:foreach node in=$nodeArray do={
$funcLogShutdown $node $stage $threshold $upsRTLeft $cDate $cTime $cTZone
$funcShutdown $srcAddr $node $srvDomain $linuxCmd
:delay 3s
}
} else={
:set stage "3"
$funcShutdownEmail $emailAddr $stage $threshold $upsRTLeft $upsLoad $cDate $cTime $cTZone device="MikroTik devices"
:delay 5s
:foreach mtik in=$mtikArray do={
$funcLogShutdown $mtik $stage $threshold $upsRTLeft $cDate $cTime $cTZone
$funcShutdown $srcAddr $mtik $mgmtDomain $mtikCmd
:delay 3s
}
:local rootDev $idName
$funcLogShutdown $rootDev $stage $threshold $upsRTLeft $cDate $cTime $cTZone
/system shutdown
}
}
} else={
:log warning "A/C power interrupted - UPS is on auxiliary power"
:log info " - UPS runtime threshold: $threshold\
\n - UPS runtime remaining: $upsRTLeft\
\n - UPS current load: $upsLoad %"
:if ($cTime < ($offlineTime + 20s)) do={
:tool e-mail send to=$emailAddr\
subject="UPS ALERT: A/C power interrupted"\
body="UPS is on auxiliary power.\
\n\nAuto-shutdown sequence will commence when the runtime threshold is reached.\
\n\n\nUPS Information:\
\n---------------------\
\nRuntime threshold: $threshold\
\nRuntime remaining: $upsRTLeft\
\nCurrent load: $upsLoad %\
\n\n\nDate: $cDate\
\nTime: $cTime ($cTZone)"
}
}
}
I don't have a UPS to test it with, but it looks fine.
Thank you much!
The style looks good, IMO. But like @rextended points out, it hard to know exact what the UPS may report - but you do have some logging (which is great) to troubleshoot.
The reporting values: runtime-left and load are consistent. This was tested many times. And the staging works perfectly.
With this script scheduled for every 30s, after stage 1 shutdown, my main server shuts down. This drastically reduces the load and increases runtime-left to about 30m.
After stage 2 shutdown, about 10-12m remain.
And then the final MikroTik stack shutdown at 5m left.
I keep more important services, (vaultwarden, authentik, traefik, ddns) on my rke2 cluster, so this tiered shutdown setup works great and preserves more time for the network in a power outage situation.
Later on, if I’m feeling brave xD I might add host recovery via WoL functions **if the power gets restored before depleting the battery. Ambitious for me though.
More than a MikroTik problem, it seems to me to be a configuration problem of the device that needs to be "repowered"...
/tool wol interface=<ifx> mac=0C:00:DE:AD:BE:EF
You need the MAC, the IPs do not work with WOL....