[solved] Script review and a question

Is there a fast way to check if a file or directory exists ?

On an USB connected SATA SSD - RB5009 device, /file find name="filename" take 3 minutes. There are almost 200.000 files on SSD: Gentoo portage, distfiles including git clones, a container etc.
Tried other variants like full path, get content, but if file does not exist, script fails and stop. Is like a try block available ?

Also, please review the script, I’m new on Mikrotik scripting, it’s a little to big for a simple task.
In console is possible to collect /container shell container-name cmd, but in script I got nothing with or without as-value.

:global wanInterface "pppoe-digi"
:global wanIP "$wanIP"
:global webIPv6 "$webIPv6"
:local isRunning 0
:local isMounted 0

#/file/add name=usb1-part1/ip type=directory
#/file/add name=usb1-part1/ip/ipv4.txt type=file
#/file/add name=usb1-part1/ip/ipv6.txt type=file

:set isMounted [/disk/print count-only where partition-size=256059113472]
:if ($isMounted = 1) do={
  :if ([:len $wanIP] = 0) do={
    :set wanIP [/file/get usb1-part1/ip/ipv4.txt contents]
    /log info "WAN IP restored from file"
  }

  :if ([:len $webIPv6] = 0) do={
    :set webIPv6 [/file/get usb1-part1/ip/ipv6.txt contents]
    /log info "Web IPv6 restored from file"
  }

  # Get the current IP on the interface
  :local currentIPtemp [/ip address get ([find where interface=$wanInterface]->0) address]
  # IP without netmask
  :local currentIP [:pick $currentIPtemp 0 ([:len $currentIPtemp]-3)]

  :set isRunning [/container print count-only where running]
  :if ($isRunning = 1) do={
    /container shell nginx cmd="ifconfig | grep inet6 | grep -v 'fe80\\|::1' | head -1 | awk '{print \$2;}' > /etc/nginx/scripts/addr.txt"
    :local tempIP [/file get usb1-part1/docker/data/nginx/scripts/addr.txt contents ]
    :local currentIPv6 [:put [ :pick $tempIP 0 [ :find $tempIP "\n" ] ] ];

    :if ($currentIP !=$wanIP or $currentIPv6 !=$webIPv6) do={
      :set wanIP $currentIP
      :set webIPv6 $currentIPv6
      /file/set usb1-part1/ip/ipv4.txt contents=$currentIP
      /file/set usb1-part1/ip/ipv6.txt contents=$currentIPv6
      /log info "WAN IP changed from $wanIP to $currentIP"
      /log info "Web IPv6 changed from $webIPv6 to $currentIPv6"
      :delay 3000
      /system script run cloudns_update
    }
  } else={
    /log error "NGINX container is not running"
  }
} else={
  /log error "SSD is not mounted. Reseat or reboot." 
}

Thank you!

You could try (with full path) to get length of file, something loosely like:
:if ([:len [/file find name=mypath/myfile.ext]] > 0) do={
:put “FOUND IT”
}
this should return 0 if file is not found and the length of file if found.

For cmd in script: /container shell nginx cmd="ifconfig | grep inet6 | grep -v 'fe80\\|::1' | head -1 | awk '{print \$2;}' > /etc/nginx/scripts/addr.txt" there won’t be any value since command stdout is redirected to file.

Also stderr is not returned as value, so if command fails (or returns output for some reason into stderr) it won’t have value eather. Try to perform for eg. :put [/container/shell nginx cmd="ls" as-value] it should work. If you need to handle both, stdout and stderr outputs as return value redirect command output with2>&1, eg. :put [/container/shell nginx cmd="bad_command 2>&1" as-value].

:if ([:len [/file find name=usb1-part1/ip/ipv4.txt ]] > 0) do={:put "something"}

still scan the entire ssd

do { :set wanIP [/file/get usb1-part1/ip/ipv4.txt contents] } on-error={ }

This is the problem :local var [/container shell nginx cmd="ifconfig | grep inet6 | grep -v 'fe80\|::1' | head -1 | awk '{print $2;}'“ as-value] ; :put $var works only in terminal, not in script. Thats why decided to redirect it to a file to have an output somehow.

In script $var is empty.

Very nice, exactly what I was looking for.
Thank you all!

Ok, will try now with script (simple ls command as I explained) but I somehow doubt that it will be difference.

Edit:

As assumed no issue executing ls command on my running Pi-hole container and assign value to variable in script:

/system/script/add dont-require-permissions=yes name=cont_cmd owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":local cmdOut [/container/shell pihole cmd=\"ls 2>&1\" as-value]\r\n:put $cmdOut\r\n"
>/system/script/run cont_cmd
bin
crontab.txt
dev
etc
home
lib
macvendor.db
media
mnt
opt
pihole.docker.tag
proc
root
run
sbin
srv
sys
tmp
usr
var

Perhaps there is reason for the complexity...

But I'm not sure why you need the WAN IP in the first place. You could just listen on 0.0.0.0 and use RouterOS firewall to restrict as needed.

Or, if it is needed in nginx config, the nginx docker image support using environment variable, see https://hub.docker.com/_/nginx#using-environment-variables-in-nginx-configuration-new-in-119 - so you might be easer to just update some /container/env on IP address change on the RouterOS side (and the cause ngnix to reload config).

Depending on how this is run... the $2 may be an empty string, since RouterOS's string interpolation is trigger before cmd is run. i.e. the variable substitution might be happening before even getting to the container's shell.

The updated script:

:global wanInterface "pppoe-digi"
:global wanIP "$wanIP"
:global webIPv6 "$webIPv6"
:local isRunning 0
:local isMounted 0

#/file/add name=usb1-part1/ip type=directory
#/file/add name=usb1-part1/ip/ipv4.txt type=file
#/file/add name=usb1-part1/ip/ipv6.txt type=file

:set isMounted [/disk/print count-only where partition-size=256059113472]
:if ($isMounted = 1) do={
  :if ([:len $wanIP] = 0) do={
    do { :set wanIP [/file/get usb1-part1/ip/ipv4.txt contents] } on-error={ 
      /log error "IPv4 informations are lost"
    }
    /log info "WAN IP restored from file"
  }

  :if ([:len $webIPv6] = 0) do={
    do { :set wanIP [/file/get usb1-part1/ip/ipv6.txt contents] } on-error={ 
      /log error "IPv6 informations are lost"
    }
    /log info "Web IPv6 restored from file"
  }

  # Get the current IP on the interface
  :local currentIPtemp [/ip address get ([find where interface=$wanInterface]->0) address]
  # IP without netmask
  :local currentIP [:pick $currentIPtemp 0 ([:len $currentIPtemp]-3)]

  :set isRunning [/container print count-only where running]
  :if ($isRunning = 1) do={
    :local tempIP [/container shell nginx cmd="ifconfig | grep inet6 | grep -v 'fe80\\|::1' | head -1 | awk '{print \$2;}'" as-value]
    :local currentIPv6 [:put [ :pick $tempIP 0 [ :find $tempIP "\n" ] ] ];

    :if ($currentIP !=$wanIP or $currentIPv6 !=$webIPv6) do={
      :set wanIP $currentIP
      :set webIPv6 $currentIPv6
      /file/set usb1-part1/ip/ipv4.txt contents=$currentIP
      /file/set usb1-part1/ip/ipv6.txt contents=$currentIPv6
      /log info "WAN IP changed from $wanIP to $currentIP"
      /log info "Web IPv6 changed from $webIPv6 to $currentIPv6"
      :delay 3000
      /system script run cloudns_update
    }
  } else={
    /log error "NGINX container is not running"
  }
} else={
  /log error "SSD is not mounted. Reseat or reboot." 
}

The file check is fast now and IPv6 address is obtained directly, without intermediate file. I don’t now what the error was.

The complexity:

  • container IPv4 is private 192.168.x.y and is behind a NAT, of course - so the need of router WAN IP for DNS;
  • IPv6 address is public and specific to container via VETH interface. I have to get that IP and send to DNS server.

Thank you all for support!

no issue with that:

/system/script/add dont-require-permissions=yes name=cont_cmd owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":local cmdOut [/container/shell pihole cmd=\"echo \\\"1 2\\\" | awk '{print \\\$2}'\" as-value]\r\n:put \$cmdOut\r\n"
>/system/script/run cont_cmd
2