Need some help finding the handler script/function that writes changes from luci to the uci config files

I have a custom wireguard script that is checking several different remote wireguard "servers" and will then automatically enable a single wireguard interface on my router based on which one has the lowest server load/ttl/speed/etc... It is protonvpn and routes all my traffic through the enabled one... it mainly to keep my ISP from snooping on me. For now, I am writing UCI commands in the scripts to automate everything but if I happen to enable a wireguard interface manually in Luci, instead of changing the option auto '0' to option auto '1' it will just delete the option completely and just falls back to the default value of 1. This is good and all but I used that value to tell my script which interface was active. I did this workaround

for key in "${!server_loads[@]}"; do
  # the key names in server_loads array match the wg interface names
  auto_config="network.${key}.auto"
  if ! auto_config_value=$(uci -S get $auto_config); then
    if  [[  $dryrun = 1 ]]; then
      log_msg "protonvpn: network uci 'auto' config not set on wg interface, but dry-run option was given so ignoring..."
    else
      log_msg  "protonvpn: network uci 'auto' config not set on wg interface, setting..."
      uci add_list ${auto_config}=1
      uci commit
      auto_config_value=$(uci get $auto_config)
    fi
  elif [[ $auto_config_value -eq 1 ]]; then
    export active_wg="$key"
    export active_load=${server_loads[$key]}
    break
  fi
done

which works as best I can tell but it is rather clunky and dirty and I would rather just change the behavior of Luci. I have looked around but can't find what is handling that. Any help would be appreciated. Thanks.

In ash (the shell used by BusyBox, common in OpenWrt), arrays are not natively supported

2 Likes

I have Bash installed... thanks for the heads up though.

1 Like

I am not a dev and do not use a vpn on openwrt but take a look in the following directories on your OpenWrt device. You may find what you are looking for or else see some clues to help you.

In general, look in /www/ and various sub-directories like /www/luci-static/resources/
Also /www/cgi-bin/ and /usr/libexec/
Some/all? menu item configurations are under /usr/share/luci/
EDIT: ucode files may be under /usr/share/rpcd/ucode/.

Many of the files are html, json, ucode, or javascript so you can edit them.

Keep in mind that modifying files provided by the system or package installation will get overwritten with upgrades etc and future versions may have differing syntax so you may need to patch/edit files in a different way then.

I'm not real clear on what you're trying to accomplish, but if you are using the standard LuCI buttons, uci commits happen in the uci_apply function here:

https://github.com/openwrt/luci/blob/master/modules/luci-base/ucode/controller/admin/uci.uc#L78

In addition to the fine response from @efahl, I'm thinking there might be application specific handling in the luci module that you are using. If it is luci module luci-proto-wireguard then the github page for it might help as well. https://github.com/openwrt/luci/tree/master/protocols/luci-proto-wireguard . Change the branch from master to 24.10.x or which ever matches your installation as these do sometimes get modified between releases.

Another thought is rather than modifying the behaviour of luci to fit your script methodology, look into querying the active wireguard interface. The scripts in the github page might reveal If wireguard is query-able via ubus or another method.

You can see what is available from ubus with ubus list. Then you can show the calls that are available with verbose mode. A random example for session is: ubus -v list session which outputs:

ubus -v list session
'session' @7c501d53
	"create":{"timeout":"Integer"}
	"list":{"ubus_rpc_session":"String"}
	"grant":{"ubus_rpc_session":"String","scope":"String","objects":"Array"}
	"revoke":{"ubus_rpc_session":"String","scope":"String","objects":"Array"}
	"access":{"ubus_rpc_session":"String","scope":"String","object":"String","function":"String"}
	"set":{"ubus_rpc_session":"String","values":"Table"}
	"get":{"ubus_rpc_session":"String","keys":"Array"}
	"unset":{"ubus_rpc_session":"String","keys":"Array"}
	"destroy":{"ubus_rpc_session":"String"}
	"login":{"username":"String","password":"String","timeout":"Integer"}

which shows the calls and the required parameters for each.

Think I figured it out with a little help searching from Grok.

I edited the 'interfaces.js' file in /www/luci-statci/resources/view/network
(starting at line: 250)

 o = s.taboption('general', form.Flag, 'auto', _('Bring up on boot'));
                o.modalonly = true;
                o.default = o.enabled;
                // I added the line below
                o.rmempty = false;

Have not had time to test it yet. Also saw a few other new replies above. Thanks, everyone I will check those out this afternoon (at work currently) and, test my solution. If it works I will mark as answered. If someone thinks my solution is not sufficient please let me know.

So, it is most def "application" specific. Not sure if you read my OP but it is morw "script specific". Here is a a good part of the script minus several functions and command line parameter handling


watchdog_function

if [[ "$nofetch" == 1 ]]; then
  if [[ -f $load_array_file ]];then
    if [[ -f $server_loads_file ]];then
      hash_test_value="$(md5sum $server_loads_file | cut -d' ' -f1)"
      source $load_array_file
      if [[ $load_file_hash != $hash_test_value ]];then
        log_msg "protonvpn:calculated array does not match most recent ${server_loads_file}, recalculating"
        calc_loads
      else
        log_msg "server load array file already exists, no need to calculate a new one"
      fi
    else
     log_msg "protonvpn:calculated array exists but no ${server_loads_file}, exists,recalculating to be safe"
     calc_loads
    fi
  else
    if [[ -f $server_loads_file ]]; then
      calc_loads
    else
      log_msg -e "protonvpn: cannot use existing data, no server_loads_file ${server_loads_file} to use"
      error_quit
    fi
  fi  
  log_msg "Using old server load info because the -n or --no-fetch option was given in command"
else
  fetch_loads
  calc_loads
fi

source $load_array_file


for key in "${!server_loads[@]}"; do
  if [[ -z "$min_load" || "${server_loads[$key]}" < "$min_load" ]]; then
    min_load="${server_loads[$key]}"
    min_load_server="$key"
  fi
  done

for key in "${!server_loads[@]}"; do
  loads_display="${loads_display} ${key}=${server_loads[$key]}%"
done

log_msg "protonvpn INFO: server with lowest load is $min_load_server with a load of ${min_load}% . Other loads are ${loads_display}"

# Hint: the array key labels are also the names of each wireguard interface name

for key in "${!server_loads[@]}"; do
  auto_config="network.${key}.auto"
  if ! auto_config_value=$(uci -S get $auto_config); then
    if  [[  $dryrun = 1 ]]; then
      log_msg "protonvpn: network uci 'auto' config not set on wg interface, but dry-run option was given so ignoring..."
    else
      log_msg  "protonvpn: network uci 'auto' config not set on wg interface, setting..."
      uci add_list ${auto_config}=1
      uci commit
      auto_config_value=$(uci get $auto_config)
    fi
  elif [[ $auto_config_value -eq 1 ]]; then
    export active_wg="$key"
    export active_load=${server_loads[$key]}
    break
  fi
done


if [[ $active_wg == $min_load_server ]] || [[ -n $active_load && $active_load -lt 50 ]]; then
  log_msg "protonvpn: current active wireguard server=${active_wg} Load=${active_load}%"
  log_msg "protonvpn INFO: server with lowest load already running, or load is less than 50 percent exiting."
  update_leds $active_load
	exit 0
elif [[ -z $active_load ]]; then
  log_msg -e "protonvpn: '${active_load}' variable is not set, someting is wrong, exiting"
  error_quit
else
  if [[ "$dryrun" = 1 ]]; then
     echo "active wg interface is: $active_wg and load is $active_load, would be changed to $min_load_server" >&2
     echo "DEBUG: not actually changing interfaces... exiting" >&2
     exit 0
  fi
  log_msg "active wg interface is: $active_wg and load is $active_load, changing to $min_load_server"
  
  uci set network.${active_wg}.auto=0
  uci set network.${min_load_server}.auto=1
  uci commit
  
  update_leds $active_load
  
  log_msg "protonvpn: restarting network..."
  /etc/init.d/network restart
  log_msg "protonvpn: wireguard protonvpn server now set to $min_load_server"
fi

Hopefully you can get a rough Idea from that.

Anyways, the wireguard specific settings do not include the "catch all" options present on all interfaces. As you can see in my script there was already an effective workaround but I wanted something cleaner.

the only side effect is it will start explicitly declaring the auto '1' for every interface in the uci config file, not just the wiregurard ones but I see no way this could cause problems