I made an openwrt rapid configuration domain specific language

I wanted a quick way to configure my openwrt via paste-to-run script method and a single action.

After working all week on this and wringing all the bugs out I created a series of high level and helper functions (keeping in mind ash's current 512 char limit per command, really complicating things).

It works for my purpose.

Which is the paste the following into my router and have it all reconfigured in one go.

setting hostname router zonename America/Iqaluit timezone EST5EDT,M3.2.0,M11.1.0 no_login_check_for_upgrades darkmode
setting rootpass qwerty rootkey "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDkmE34AKFm2Qwt93MNF8CSy60pRJGNMm799AAxLhwoDUi1Hqux7QgUpafvwd7bPruIGx5P+Z2yRg6Dfmk+rCRgWRZ0Fs4VgKOFRsirRP33bmGGfmhh81BmwVmPJF+cAZW11kuVPFW64WDJEGIgO3z9oClG9Zt62nghccDoZsTVXNA28n9DgXEcJloVTVUOtRmW6Q3Ofy6tXgaRhtOpThIV7VAWFq80YooBmH8AuOjwyjT3MFCAmaa22Eb5B0o6fhANzFT3HtXmsT3O9BLUwnCOCp1ZdSm+cMxA/m1lJD0k9G3dVxvMhR9ETo2SFEtZsxZk0UeLBT/tVjsaeMGY5rEn rsa-key-20211218"
interface lan protocol static clear ipaddr ipaddr 10.0.0.1 netmask 255.255.0.0
interface wan protocol static clear ipaddr ipaddr 100.59.28.17 netmask 255.255.240.0 gateway 100.59.28.1 dns 8.8.8.8
device wan mac B0:0F:B0:0B:50:01
interface public_loopback protocol static device lo clear ipaddr ipaddr 96.96.69.69 netmask 255.255.255.255
nat public_snat src wan target SNAT snatip 96.96.69.69
dhcp lan dhcpv6 disabled ra disabled ndp disabled
radio radio0 band 2g channel auto htmode HE20 enable country CA no_legacy_rates no_disable
radio radio1 band 5g channel auto htmode HT40 enable country CA no_legacy_rates no_disable
wap default_radio0 ssid 662 encryption psk key muchwifi
wap default_radio1 ssid 662 encryption psk key muchwifi
forward sunshine dest lan target DNAT src wan src_dport 47984-48010 dest_ip 10.0.0.243
forward mumble dest lan target DNAT src wan src_dport 64738 dest_ip 10.0.0.10
forward josunshine dest lan target DNAT src wan src_dport 47984-48010 dest_ip 10.0.0.117 dest_port 47984-48010
forward Valve dest lan target DNAT src wan src_dport 27000-27037 dest_ip 10.0.0.10
forward riftbreaker dest lan target DNAT src wan src_dport 6321 dest_ip 10.0.0.10
forward web dest lan target DNAT src wan src_dport 80 dest_ip 10.0.0.101
dns domain chromebox.lan ip 192.168.1.9
dns domain proxwin.lan ip 192.168.1.21
dns domain wapn ip 192.168.1.2
dns domain wapn ip 192.168.1.2
tag match match option:client-arch,1 networkid NEC_PC98
boot filename NEC_PC98.efi networkid NEC_PC98
tag match match option:client-arch,2 networkid Itanium
boot filename Itanium.efi networkid Itanium
tag match match option:client-arch,3 networkid DEC_Alpha
boot filename DEC_Alpha.efi networkid DEC_Alpha
tag match match option:client-arch,4 networkid Arc_x86
boot filename Arc_x86.efi networkid Arc_x86
tag match match option:client-arch,5 networkid Lean_Client
boot filename Lean_Client.efi networkid Lean_Client
tag match match option:client-arch,6 networkid UEFI_x86
boot filename ipxe.efi networkid UEFI_x86
tag match match option:client-arch,7 networkid UEFI_x64
boot filename ipxe.efi networkid UEFI_x64
tag match match option:client-arch,8 networkid UEFI_Xscale
boot filename UEFI_Xscale.efi networkid UEFI_Xscale
tag match match option:client-arch,9 networkid EBC
boot filename EBC.efi networkid EBC
tag match match option:client-arch,10 networkid ARM32_EFI
boot filename bootaa32.efi networkid ARM32_EFI
tag match match option:client-arch,11 networkid ARM64_EFI
boot filename bootaa64.efi networkid ARM64_EFI
tag match match option:client-arch,12 networkid PowerPC_OFW
boot filename PowerPC_OFW.efi networkid PowerPC_OFW
tag match match option:client-arch,13 networkid PowerPC_ePAPR
boot filename PowerPC_ePAPR.efi networkid PowerPC_ePAPR
tag match match option:client-arch,14 networkid POWER_OPAL_v3
boot filename POWER_OPAL_v3.efi networkid POWER_OPAL_v3
tag match match option:client-arch,15 networkid UEFI_x86_http
boot filename ipxe.efi networkid UEFI_x86_http
tag match match option:client-arch,16 networkid UEFI_x64_http
boot filename ipxe.efi networkid UEFI_x64_http
tag match match option:client-arch,17 networkid EBC_http
boot filename EBC_http.efi networkid EBC_http
tag match match option:client-arch,18 networkid ARM32_http
boot filename bootaa32.efi networkid ARM32_http
tag match match option:client-arch,19 networkid ARM64_http
boot filename bootaa64.efi networkid ARM64_http
tag match match option:client-arch,20 networkid PCAT_BIOS_http
boot filename ipxe.pxe networkid PCAT_BIOS_http
tag match match option:client-arch,21 networkid UBoot_ARM32
boot filename UBoot_ARM32.efi networkid UBoot_ARM32
tag match match option:client-arch,22 networkid UBoot_ARM64
boot filename UBoot_ARM64.efi networkid UBoot_ARM64
tag match match option:client-arch,23 networkid UBoot_ARM32_http
boot filename UBoot_ARM32_http.efi networkid UBoot_ARM32_http
tag match match option:client-arch,24 networkid UBoot_ARM64_http
boot filename UBoot_ARM64_http.efi networkid UBoot_ARM64_http
tag match match option:client-arch,25 networkid RISC-V_32
boot filename RISC-V_32.efi networkid RISC-V_32
tag match match option:client-arch,26 networkid RISC-V_32_http
boot filename RISC-V_32_http.efi networkid RISC-V_32_http
tag match match option:client-arch,27 networkid RISC-V_64
boot filename RISC-V_64.efi networkid RISC-V_64
tag match match option:client-arch,28 networkid RISC-V_64_http
boot filename RISC-V_64_http.efi networkid RISC-V_64_http
tag match match option:client-arch,29 networkid RISC-V_128
boot filename RISC-V_128.efi networkid RISC-V_128
tag match match option:client-arch,30 networkid RISC-V_128_http
boot filename RISC-V_128_http.efi networkid RISC-V_128_http
tag match match option:client-arch,31 networkid s390_Basic
boot filename s390_Basic.efi networkid s390_Basic
tag match match option:client-arch,32 networkid s390_Extended
boot filename s390_Extended.efi networkid s390_Extended
tag match match option:client-arch,33 networkid MIPS_32
boot filename MIPS_32.efi networkid MIPS_32
tag match match option:client-arch,34 networkid MIPS_64
boot filename MIPS_64.efi networkid MIPS_64
tag match match option:client-arch,35 networkid Sunway_32
boot filename Sunway_32.efi networkid Sunway_32
tag match match option:client-arch,36 networkid Sunway_64
boot filename Sunway_64.efi networkid Sunway_64
tag match match option:client-arch,37 networkid LoongArch_32
boot filename LoongArch_32.efi networkid LoongArch_32
tag match match option:client-arch,38 networkid LoongArch_32_http
boot filename LoongArch_32_http.efi networkid LoongArch_32_http
tag match match option:client-arch,39 networkid LoongArch_64
boot filename LoongArch_64.efi networkid LoongArch_64
tag match match option:client-arch,40 networkid LoongArch_64_http
boot filename LoongArch_64_http.efi networkid LoongArch_64_http
tag match match option:client-arch,41 networkid arm_rpiboot
boot filename arm_rpiboot.efi networkid arm_rpiboot
tag match match option:user-class,iPXE networkid iPXE_client
lease cam1 mac B0:0F:B0:0B:50:02 ip 192.168.1.81 name cam1 dns tag deadend_gateway
lease cam10 mac B0:0F:B0:0B:50:03 ip 192.168.1.90 name cam10 dns tag deadend_gateway
lease cam11 mac B0:0F:B0:0B:50:04 ip 192.168.1.91 name cam11 dns tag deadend_gateway
lease cam12 mac B0:0F:B0:0B:50:05 ip 192.168.1.92 name cam12 dns tag deadend_gateway
lease cam13 mac B0:0F:B0:0B:50:06 ip 192.168.1.93 name cam13 dns tag deadend_gateway
lease cam14 mac B0:0F:B0:0B:50:07 ip 192.168.1.94 name cam14 dns tag deadend_gateway
lease cam15 mac B0:0F:B0:0B:50:08 ip 192.168.1.95 name cam15 dns tag deadend_gateway
lease cam2 mac B0:0F:B0:0B:50:09 ip 192.168.1.82 name cam2 dns tag deadend_gateway
lease cam3 mac B0:0F:B0:0B:50:0A ip 192.168.1.83 name cam3 dns tag deadend_gateway
lease cam4 mac B0:0F:B0:0B:50:0B ip 192.168.1.84 name cam4 dns tag deadend_gateway
lease cam5 mac B0:0F:B0:0B:50:0C ip 192.168.1.85 name cam5 dns tag deadend_gateway
lease cam6 mac B0:0F:B0:0B:50:0D ip 192.168.1.86 name cam6 dns tag deadend_gateway
lease cam7 mac B0:0F:B0:0B:50:0E ip 192.168.1.87 name cam7 dns tag deadend_gateway
lease cam8 mac B0:0F:B0:0B:50:0F ip 192.168.1.88 name cam8 dns tag deadend_gateway
lease cam9 mac B0:0F:B0:0B:50:10 ip 192.168.1.89 name cam9 dns tag deadend_gateway
lease chambre mac B0:0F:B0:0B:50:11 ip 192.168.1.12 name chambre dns
lease comprouter mac B0:0F:B0:0B:50:12 ip 192.168.1.12 name comprouter dns
lease dead1 tag deadgateway mac B0:0F:B0:0B:50:13
lease dead2 mac B0:0F:B0:0B:50:14 tag deadgateway
lease dead3 mac B0:0F:B0:0B:50:15 tag deadgateway
lease dead4 mac B0:0F:B0:0B:50:16 tag deadgateway
lease flip mac B0:0F:B0:0B:50:17 ip 192.168.1.30 name flip dns
lease host217 mac B0:0F:B0:0B:50:18 ip 192.168.1.217
lease host3 mac B0:0F:B0:0B:50:19 ip 192.168.1.3
lease host31 mac B0:0F:B0:0B:50:1A ip 192.168.1.31
lease host55 mac B0:0F:B0:0B:50:1B ip 192.168.1.55 tag deadend_gateway
lease ilo1 mac B0:0F:B0:0B:50:1C ip 192.168.1.61 name ilo1 dns
lease ilo2 mac B0:0F:B0:0B:50:1D ip 192.168.1.62 name ilo2 dns
lease ilo3 mac B0:0F:B0:0B:50:1E ip 192.168.1.63 name ilo3 dns
lease ilo4 mac B0:0F:B0:0B:50:1F ip 192.168.1.64 name ilo4 dns
lease nginx mac B0:0F:B0:0B:50:22 ip 192.168.1.100 name nginx
lease pc1 mac B0:0F:B0:0B:50:23 ip 10.0.0.10
lease proxa mac B0:0F:B0:0B:50:24 ip 192.168.1.8 name proxa dns
lease proxb mac B0:0F:B0:0B:50:25 ip 192.168.1.120 name proxb dns
lease proxmox mac B0:0F:B0:0B:50:26 ip 192.168.1.20 name proxmox dns
lease proxmox3 mac B0:0F:B0:0B:50:27 ip 192.168.1.23 name proxmox3 dns
lease routerhost mac B0:0F:B0:0B:50:28 ip 192.168.1.15 name routerhost dns
lease screenll mac B0:0F:B0:0B:50:29 ip 192.168.1.231 name screenll
lease screenlr mac B0:0F:B0:0B:50:2A ip 192.168.1.233 name screenlr
lease screenul mac B0:0F:B0:0B:50:2B ip 192.168.1.230 name screenul
lease screenur mac B0:0F:B0:0B:50:2C ip 192.168.1.232 name screenur
lease shodan mac B0:0F:B0:0B:50:2D ip 192.168.1.10 name shodan dns
lease srv1 ip 192.168.1.71 name srv1 dns
lease srv2 ip 192.168.1.72 name srv2 dns
lease srv3 ip 192.168.1.73 name srv3 dns
lease srv4 ip 192.168.1.74 name srv4 dns
lease tv mac B0:0F:B0:0B:50:2E ip 192.168.1.7 name tv
lease wapn mac B0:0F:B0:0B:50:2F ip 192.168.1.2 name wapn dns
lease webcam mac B0:0F:B0:0B:50:30 ip 10.0.0.182 name webcam
lease win10 mac B0:0F:B0:0B:50:31 ip 192.168.1.99 name win10 dns tag deadend_gateway
lease win10test mac B0:0F:B0:0B:50:32 ip 192.168.1.40 name win10test dns tag deadend_gateway
lease win10test2 name win10test2 dns tag deadend_gateway
lease wintest mac B0:0F:B0:0B:50:33 ip 192.168.1.98 name wintest dns tag deadend_gateway
lease yak mac B0:0F:B0:0B:50:34 ip 192.168.1.6 name yak dns
lease z3 mac B0:0F:B0:0B:50:35 ip 192.168.1.11 name z3 dns
lease zold mac B0:0F:B0:0B:50:36 ip 192.168.1.19 name zold dns
tag deadend_gateway dhcp_option 3,192.168.1.254 dhcp_option 6,192.168.1.1
tag deadgateway dhcp_option 3,10.0.0.254
lease deadmulti tag deadgateway mac B0:0F:B0:0B:50:37 B0:0F:B0:0B:50:38 B0:0F:B0:0B:50:39
disable odhcpd
install nano
/etc/init.d/system reload #reset hostname
saveandapply

And to do this, I have these high-level functions, here is how they are to be used.

# boot [boot] <key> <value>... ; Configure dnsmasq boot or create boot entry bound to default dnsmasq instance
# boot6 <key> <value>... ; Configure DHCPv6 boot settings
# bridge_vlan <device> vlan <vid> <key> <value>... ; Create or modify bridge VLAN entry for device and VLAN ID
# device <name> <key> <value>... ; Create or modify network device by name
# dhcp <interface> <key> <value>... ; Configure DHCP settings for a given interface
# dhcp6 <key> <value>... ; Configure global odhcpd (DHCPv6) settings
# dhcprelay <key> <value>... ; Create or modify DHCP relay tied to default dnsmasq instance
# disable <target> ; Disable a feature, setting, or service via lookup or init script
# dns [instance <name>] <key> <value>... ; Configure dnsmasq instance or default dnsmasq section
# enable <target> ; Enable a feature, setting, or service via lookup or init script
# firewall <name> <key> <value>... ; Create or modify firewall rule by name
# forward <name> <key> <value>... ; Create or modify firewall redirect (port forward) by name
# install <pkg>... ; Install one or more packages using apk
# interface <name> <key> <value>... ; Create or modify network interface by name
# ipset <name> <key> <value>... ; Create or modify firewall ipset by name
# lease <id> <key> <value>... ; Create or modify DHCP host (static lease) identified by id
# led <name> <key> <value>... ; Create or modify system LED configuration by name
# nat <name> <key> <value>... ; Create or modify firewall NAT rule by name
# radio <radio> [enable|disable] <key> <value>... ; Configure wireless radio and optionally toggle enabled state
# routes <interface> <key> <value>... ; Create route entry for interface (always creates new anonymous section)
# routing <key> <value>... ; Create routing rule (always creates new anonymous section)
# saveandapply ; Show pending UCI changes, commit them, and reload all relevant services
# setting <key> [value] ; Apply global/system setting via lookup, special handler, or direct UCI assignment
# station <name> <key> <value>... ; Create or modify wireless station by name
# switch [name] <key> <value>... ; Configure existing network switch (default switch0)
# switch_vlan <device> vlan <vid> <key> <value>... ; Create or modify switch VLAN entry for device and VLAN ID
# tag [instance <name>] (user|vendor|match|<tagname>) <key> <value>... ; Create or modify DHCP tag, match, or class entry
# wap <name> [enable|disable] <key> <value>... ; Create or modify wireless AP (wifi-iface) and optionally toggle state
# wvlan <name> <key> <value>... ; Create or modify wireless VLAN by name
# zone <name> [forward_to <zones>|forward_from <zones>] <key> <value>... ; Create or modify firewall zone and its forwarding rules

A few have buggy edge cases and inconsistency, I tried to make them all idempotent but some like routes routing bridge_vlan switch_vlan are not.

And here is the actual list of functions that you paste BEFORE your actual config script


#lookup lists
BOOLS_BRIDGE="bridge_empty vlan_filtering igmp_snooping multicast_querier stp"; 
BOOLS_DEV="rxpause txpause autoneg promisc acceptlocal sendredirects arp_accept drop_gratuitous_arp multicast ip6segmentrouting drop_unsolicited_na proxy_arp";
BOOLS_DNS="domainneeded boguspriv filterwin2k localise_queries rebind_protection rebind_localhost expandhosts nonegcache authoritative readethers nonwildcard localservice filter_aaaa filter_a cachelocal dnssec dnsseccheckunsigned enable_tftp logqueries nodaemon nohosts noresolv strictorder allservers quietdhcp sequential_ip addmac logdhcp dynamicdhcp noping dhcpv6";
BOOLS_FIREWALL="counters masq masq6 masq_allow_invalid mtu_fix auto_helper reflection enabled log drop_invalid syn_flood synflood_protect tcp_syncookies tcp_window_scaling accept_redirects accept_source_route custom_chains disable_ipv6 flow_offloading flow_offloading_hw auto_includes utc_time onlink invert";
BOOLS_NET="auto disabled ipv6 force_link defaultroute defaultroute6 peerdns packet_steering reflection_zone delegate"; 
BOOLS_RA="force ra_slaac ra_dns dns_service"; 
BOOLS_SYSTEM="log enabled cronlog urandom_seed"; 
BOOLS_UPGRADE="login_check_for_upgrades auto_search advanced_mode"; 
BOOLS_WIFI2="mu_beamformer rx_antenna_pattern rxldpc short_gi_160 short_gi_20 short_gi_40 short_gi_80 su_beamformee su_beamformer tx_antenna_pattern tx_stbc_2by1 vht_txop_ps he_su_beamformer he_su_beamformee he_mu_beamformer he_bss_color twt_responder rx_stbc tx_stbc country_ie diversity frameburst greenfield legacy_rates noscan dsss_cck_40 htc_vht ldpc max_amsdu mu_beamformee multi_ap proxy_arp";
BOOLS_WIFI="masq mtu_fix masq6 masq_allow_invalid auto_helper log enabled wps_label wps_pushbutton ppsk wmm hidden isolate bridge_isolate doth rsn_preauth skip_inactivity_poll ieee80211k rrm_neighbor_report rrm_beacon_report ieee80211v wnm_sleep_mode bss_transition pmk_r1_push ft_over_ds ft_psk_generate_local ieee80211r wds start_disabled default_disabled disassoc_low_ack sae_require_mfp short_preamble";
BOOLS="$BOOLS_NET $BOOLS_BRIDGE $BOOLS_DEV $BOOLS_DNS $BOOLS_WIFI $BOOLS_WIFI2 $BOOLS_RA $BOOLS_FIREWALL $BOOLS_SYSTEM $BOOLS_UPGRADE"
LOOKUPS_FIREWALL="fwinput:firewall.@defaults[0].input fwoutput:firewall.@defaults[0].output fwforward:firewall.@defaults[0].forward fwlog:firewall.@defaults[0].log fwlog_limit:firewall.@defaults[0].log_limit fwlog_level:firewall.@defaults[0].log_level drop_invalid:firewall.@defaults[0].drop_invalid flow_offloading:firewall.@defaults[0].flow_offloading flow_offloading_hw:firewall.@defaults[0].flow_offloading_hw synflood_protect:firewall.@defaults[0].synflood_protect"
LOOKUPS_FIREWALL2="synflood_rate:firewall.@defaults[0].synflood_rate synflood_burst:firewall.@defaults[0].synflood_burst tcp_syncookies:firewall.@defaults[0].tcp_syncookies tcp_window_scaling:firewall.@defaults[0].tcp_window_scaling accept_redirects:firewall.@defaults[0].accept_redirects accept_source_route:firewall.@defaults[0].accept_source_route custom_chains:firewall.@defaults[0].custom_chains disable_ipv6:firewall.@defaults[0].disable_ipv6 auto_helper:firewall.@defaults[0].auto_helper"
LOOKUPS_FIREWALL3="mtu_fix:firewall.@defaults[0].mtu_fix reflection:firewall.@defaults[0].reflection enabled:firewall.@defaults[0].enabled tcp_reject_code:firewall.@defaults[0].tcp_reject_code any_reject_code:firewall.@defaults[0].any_reject_code"
LOOKUPS_LUCI="language:luci.main.lang tablefilter:luci.main.tablefilters";
LOOKUPS_NET="packet_steering:network.globals.packet_steering vlan:network.@switch[0].enable_vlan"; 
LOOKUPS_NTP="ntp:system.ntp.enable_server ntpd:system.ntp.enable_server"; 
LOOKUPS_SPECIAL="darkmode:luci.main.mediaurlbase:/luci-static/bootstrap-dark:/luci-static/bootstrap-light rootpass:@rootpass rootkey:@rootkey";
LOOKUPS_SYSTEM="hostname:system.@system[0].hostname zonename:system.@system[0].zonename timezone:system.@system[0].timezone log_proto:system.@system[0].log_proto conloglevel:system.@system[0].conloglevel cronloglevel:system.@system[0].cronloglevel log_size:system.@system[0].log_size log_ip:system.@system[0].log_ip log_port:system.@system[0].log_port log_file:system.@system[0].log_file description:system.@system[0].description notes:system.@system[0].notes hourcycle:system.@system[0].clock_hourcycle";
LOOKUPS_UPGRADE="login_check_for_upgrades:attendedsysupgrade.client.login_check_for_upgrades auto_search:attendedsysupgrade.client.auto_search advanced_mode:attendedsysupgrade.client.advanced_mode upgradeserver:attendedsysupgrade.server.url"; 
LOOKUPS="$LOOKUPS_NET $LOOKUPS_SYSTEM $LOOKUPS_LUCI $LOOKUPS_NTP $LOOKUPS_UPGRADE $LOOKUPS_FIREWALL $LOOKUPS_FIREWALL2 $LOOKUPS_FIREWALL3";
LISTS="mac ipaddr dns ra_flags dhcp_option dhcp_option_force server address ipset addnhosts bogusnxdomain rebind_domain addnmount ntpserver rebuilder network ports maclist tag interface src_ip dest_ip src_mac icmp_type weekdays monthdays match entry"
ALIASES="protocol:proto mask:netmask iface:ifname gw:gateway user:username pass:password pw:password swports:ports snatip:snat_ip" #removed mac:macaddr ip:ipaddr
ACTIONS="dnsmasq:service odhcpd:service"
#helper functions
uci_add(){ uci add "$@"; }; 
uci_apply_kv(){ local S K V E T F v; S="$1"; K=$(uci_resolve_key "$2"); V="$3"; E=$(lookup_entry "$LOOKUPS" "$K")&&K="$E"; uci_handle_bool "$K" "$S" "$V"&&return 1; T=$(uci_build_target "$K" "$S"); [ -z "$V" ]&&return 255; V=$(uci_normalize_list "$K" "$V"); uci_vals "$V"|while IFS='|' read -r F v; do [ -z "$v" ]&&continue; [ "$F" = 1 ]&&uci add_list "$T=$v"||uci set "$T=$v"; done; return 2; }
uci_bool(){ case " $BOOLS " in *" $2 "*) uci_set "$1.$2" "${3:-1}"; return 0;; esac; return 1; };
uci_build_target(){ case "$1" in *.*) printf '%s\n' "$1";; *) [ -n "$2" ]&&printf '%s.%s\n' "$2" "$1"||printf '%s\n' "$1";; esac; };
uci_do(){ local S; S=$(uci_section "$1" "$2" "$3"); shift 3; uci_parse_kv "$S" "$@"; }
uci_del(){ uci -q delete "$1"; }; 
uci_del_if(){ uci -q delete "$1" 2>/dev/null; }; 
uci_dsec(){ local S; [ "$3" = instance ]&&{ S="$4"; shift 4; uci_exists "$1.$S"||{ echo "Instance $S not found"; return 1; }; echo "$S|$*"; return; }; S=$(uci -q show "$1"|sed -n "s/^$1\.\([^=]*\)=$2$/\1/p"|head -n1); [ "$S" ]||{ echo "No default $2 section"; return 1; }; shift 2; echo "$S|$*"; };
uci_ensure(){ uci_exists "$1"||uci_set "$1" "$2"; }; 
uci_exists(){ uci -q get "$1" >/dev/null 2>&1; }; 
uci_get(){ uci -q get "$1"; }; 
uci_handle_bool(){ case "$1" in no_*) uci_bool "$2" "${1#no_}" 0&&return 0;; esac; case "$3" in no_*) uci_bool "$2" "${3#no_}" 0&&return 0;; esac; uci_bool "$2" "$1" "$3"&&return 0; return 1; };
uci_is_bool(){ case " $BOOLS " in *" $1 "*) return 0;; *) return 1;; esac; };
uci_normalize_list(){ case " $LISTS " in *" ${1##*.} "*) case "$2" in +=*) ;; =*) printf '+=%s\n' "${2#=}"; return;; +*) ;; *) printf '+%s\n' "$2"; return;; esac;; esac; printf '%s\n' "$2"; };
uci_oneline(){ printf '%s\n' "$1" | head -n1; };
uci_parse_kv(){ local S K V; S="$1"; shift; while [ $# -gt 0 ]; do case "$1" in clear) shift; [ $# -eq 0 ]&&{ echo "Invalid: clear"; return; }; uci delete "$S.$1" 2>/dev/null; shift; continue;; esac; K="$1"; shift; case "$K" in no_*) K="${K#no_}"; V=0;; *) if uci_is_bool "$K"||lookup_entry "$LOOKUPS_SPECIAL" "$K" >/dev/null; then V=1; else [ $# -gt 0 ]||{ echo "Invalid: $K"; return; }; V="$1"; shift; fi;; esac; uci_apply_kv "$S" "$K" "$V"; [ $? = 255 ]&&echo "Invalid: $K"; done; }
uci_resolve_key(){ local K; K=$(lookup_entry "$ALIASES" "$1"); K="${K:-$1}"; printf '%s\n' "$K"; };
uci_revert(){ local f; [ "$#" -gt 0 ]&&uci revert "$@"||{ for f in /tmp/.uci/*; do [ -e "$f" ]||continue; uci revert "${f##*/}"; done; }; };
uci_section(){ local p="$1" t="$2" n="$3" l id type; if [ "$n" ]; then type=$(uci_type "$p.$n") && [ "$type" = "$t" ] && { echo "$p.$n"; return; }; for l in $(uci -q show "$p"); do case "$l" in $p.@$t*".name='$n'") echo "${l%%.name=*}"; return;; esac; done; id=$(uci add "$p" "$t"); uci set "$p.$id.name=$n"; echo "$p.$id"; return; fi; id=$(uci add "$p" "$t"); echo "$p.$id"; }
uci_set(){ uci set "$1=$2"; }; 
uci_target(){ case "$1" in *.*) printf '%s\n' "$1";; *) [ -z "$2" ]&&return 1; printf '%s\n' "$2.$1";; esac; };
uci_type(){ uci -q get "$1" 2>/dev/null; }
uci_vals(){ local V="$1" F=0 SPLIT=0 N=0 v; case "$V" in +=*) F=1; N=1; V="${V#'+='}";; +*) F=1; SPLIT=1; V="${V#+}";; =*) N=1; V="${V#=}";; esac; [ -z "$V" ]&&return 1; if [ "$N" = 1 ]; then printf '%s|%s\n' "$F" "$V"; return; fi; if [ "$SPLIT" = 1 ]; then for v in $V; do [ -n "$v" ]&&printf '%s|%s\n' "$F" "$v"; done; else printf '%s|%s\n' "$F" "$V"; fi; };
#more helpers
action_service(){ local S; S="/etc/init.d/$1"; [ -x "$S" ] || return; [ -n "$3" ] && "$S" "$3" 2>/dev/null; [ -n "$4" ] && "$S" "$4" 2>/dev/null; };
apply_action(){ local T F; T=$(lookup_entry "$ACTIONS" "$1")||return 1; F="action_$T"; command -v "$F" >/dev/null 2>&1 || return 1; "$F" "$@"; };
apply_special(){ local F P R V1 V0; case "$1" in @*) F="setting_${1#@}"; command -v "$F" >/dev/null 2>&1 && "$F" "$2";; *:*) P="${1%%:*}"; R="${1#*:}"; V1="${R%%:*}"; V0="${R#*:}"; [ "$2" = 1 ] && uci_set "$P" "$V1" || uci_set "$P" "$V0";; esac; };
lookup_entry(){ local e; case " $1 " in *" $2:"*) for e in $1; do case "$e" in $2:*) printf '%s\n' "${e#*:}"; return 0;; esac; done;; esac; return 1; };
req(){ [ "$1" ] || { echo "Missing $2"; return 1; }; }
setting_rootkey(){ mkdir -p /etc/dropbear; grep -qxF "$1" /etc/dropbear/authorized_keys 2>/dev/null || printf '%s\n' "$1" >> /etc/dropbear/authorized_keys; };
setting_rootpass(){ (printf '%s\n%s\n' "$1" "$1") | passwd root; };
toggle_apply(){ local E S; E=$(lookup_entry "$LOOKUPS" "$1")||E=; [ "$E" ]&&{ uci_set "$E" "$2"; return; }; S=$(lookup_entry "$LOOKUPS_SPECIAL" "$1")||S=; [ "$S" ]&&{ apply_special "$S" "$2"; return; }; apply_action "$1" "$2" "$3" "$4"||return 1; };
zone_fwd(){ local dir="$1" Z="$2" list="$3" x S; for x in $list; do S=$(uci_section firewall forwarding); [ "$dir" = to ] && { uci set "$S.src=$Z"; uci set "$S.dest=$x"; } || { uci set "$S.src=$x"; uci set "$S.dest=$Z"; }; done; }
#high-level functions
boot(){ local R S T; R=$(uci_dsec dhcp dnsmasq "$@")||{ echo "No default dnsmasq instance"; return 1; }; S=${R%%|*}; set -- ${R#*|}; [ "$1" = boot ]&&{ shift; T=$(uci_section dhcp boot); uci_set "$T.instance" "$S"; }||T="dhcp.$S"; uci_parse_kv "$T" "$@"; };
boot6(){ local T; T=$(uci_section dhcp boot6); uci_parse_kv "$T" "$@"; };
bridge_vlan(){ local D VID S; D="$1"; shift; [ "$1" = vlan ]||{ echo "Invalid: vlan"; return; }; VID="$2"; shift 2; S=$(uci_section network bridge-vlan); uci set "$S.device=$D"; uci set "$S.vid=$VID"; uci_parse_kv "$S" "$@"; }
device(){ local n; n="$1"; shift||return 1; req "$n" "device name"||return 1; uci_do network device "$n" "$@"; }
dhcp(){ local n; n="$1"; shift||return 1; req "$n" "interface"||return 1; uci_do dhcp dhcp "$n" "$@"; }
dhcp6(){ uci_exists dhcp.odhcpd||{ echo "odhcpd section not found"; return 1; }; uci_parse_kv dhcp.odhcpd "$@"; };
dhcprelay(){ local R S T; R=$(uci_dsec dhcp dnsmasq "$@")||{ echo "No default dnsmasq instance"; return 1; }; S=${R%%|*}; set -- ${R#*|}; T=$(uci_section dhcp relay); uci_set "$T.instance" "$S"; uci_parse_kv "$T" "$@"; };
disable(){ [ "$1" ]||{ echo "Missing disable target"; return 1; }; toggle_apply "$1" 0 disable stop || { echo "Unknown disable target: $1"; return 1; }; };
dns(){ local n S; if [ "$1" = instance ]; then shift; n="$1"; shift||{ echo "Missing instance name"; return 1; }; S=$(uci_section dhcp dnsmasq "$n"); else uci -q get dhcp.@dnsmasq[0] >/dev/null 2>&1||{ echo "No dnsmasq section"; return 1; }; S="dhcp.@dnsmasq[0]"; fi; uci_parse_kv "$S" "$@"; };
enable(){ [ "$1" ]||{ echo "Missing enable target"; return 1; }; toggle_apply "$1" 1 enable start || { echo "Unknown enable target: $1"; return 1; }; };
firewall(){ local N; N="$1"; shift||return 1; req "$N" "rule name"||return 1; uci_do firewall rule "$N" "$@"; }
forward(){ local N; N="$1"; shift||return 1; req "$N" "forward name"||return 1; uci_do firewall redirect "$N" "$@"; }
install(){ [ $# -eq 0 ]&&{ echo "No packages specified"; return 1; }; apk add "$@"; };
interface(){ local n; n="$1"; shift||return 1; req "$n" "interface"||return 1; uci_do network interface "$n" "$@"; }
ipset(){ local N; N="$1"; shift; uci_do firewall ipset "$N" "$@"; }
lease(){ local id="$1" S; shift; S=$(uci -q show dhcp | grep "=host" | grep ".name='$id'" | cut -d. -f2 | cut -d= -f1 | head -n1); [ -z "$S" ]&&{ S=$(uci add dhcp host); uci set dhcp.$S.name="$id"; }; uci_parse_kv "dhcp.$S" "$@"; };
led(){ local n; n="$1"; shift||return 1; req "$n" "led name"||return 1; uci_do system led "$n" "$@"; }
nat(){ local N; N="$1"; shift||return 1; req "$N" "nat name"||return 1; uci_do firewall nat "$N" "$@"; }
radio(){ local R; R="$1"; shift||return 1; [ "$R" ]||{ echo "Missing radio"; return 1; }; while [ $# -gt 0 ]; do case "$1" in enable) uci_del wireless.$R.disabled; shift; continue;; disable) uci_set wireless.$R.disabled 1; shift; continue;; esac; uci_parse_kv wireless.$R "$@"; break; done; };
routes(){ local N S; N="$1"; shift||return 1; req "$N" "interface"||return 1; S=$(uci_section network route); uci set "$S.interface=$N"; uci_parse_kv "$S" "$@"; }
routing(){ local S; S=$(uci_section network rule); uci_parse_kv "$S" "$@"; }
saveandapply(){ echo "=== UCI CHANGES ==="; uci changes; echo "=== COMMITTING ==="; uci commit; echo "=== APPLYING ==="; /etc/init.d/network reload; /etc/init.d/firewall reload; /etc/init.d/dnsmasq restart; /etc/init.d/odhcpd restart 2>/dev/null; /etc/init.d/uhttpd reload 2>/dev/null; wifi reload 2>/dev/null || wifi; echo "=== DONE ==="; };
setting(){ local K SPL L V; [ "$1" ]||return; K="$1"; shift; SPL=$(lookup_entry "$LOOKUPS_SPECIAL" "$K")||SPL=; [ "$SPL" ]&&{ V="${1:-1}"; apply_special "$SPL" "$V"; return; }; L=$(lookup_entry "$LOOKUPS" "$K")||L=; [ "$L" ]&&{ [ $# -le 1 ]&&{ V="${1:-1}"; uci_apply_kv "" "$L" "$V"; return; }; }; uci_parse_kv "" "$K" "$@"; };
station(){ local n; n="$1"; shift||return 1; req "$n" "station name"||return 1; uci_do wireless wifi-station "$n" "$@"; }
switch(){ local n; n="${1:-switch0}"; shift; uci_exists "network.$n"||{ echo "Switch $n not found"; return 1; }; uci_parse_kv "network.$n" "$@"; };
switch_vlan(){ local D VID S; D="$1"; shift; [ "$1" = vlan ]||{ echo "Invalid: vlan"; return; }; VID="$2"; shift 2; S=$(uci_section network switch_vlan); uci set "$S.device=$D"; uci set "$S.vid=$VID"; uci_parse_kv "$S" "$@"; }
tag(){ local I T TGT N S; [ "$1" = instance ]&&{ shift; I="$1"; shift; }; while [ $# -gt 0 ]; do case "$1" in user|vendor|match) case "$1" in user)T=userclass;; vendor)T=vendorclass;; match)T=match;; esac; shift; TGT=$(uci_section dhcp $T); [ "$I" ]&&uci set "$TGT.instance=$I"; uci_parse_kv "$TGT" "$@"; break;; *) N="$1"; shift; S=$(uci_section dhcp tag "$N"); [ "$I" ]&&uci set "$S.instance=$I"; uci_parse_kv "$S" "$@"; break;; esac; done; }
wap(){ local n S; n="$1"; shift||return 1; [ "$n" ]||{ echo "Missing iface"; return 1; }; S=$(uci_section wireless wifi-iface "$n"); while [ $# -gt 0 ]; do case "$1" in enable) uci_del "$S.disabled"; shift;; disable) uci_set "$S.disabled" 1; shift;; *) uci_parse_kv "$S" "$@"; break;; esac; done; };
wvlan(){ local n; n="$1"; shift||return 1; req "$n" "vlan name"||return 1; uci_do wireless wifi-vlan "$n" "$@"; }
zone(){ local Z S; Z="$1"; shift||return 1; req "$Z" "zone name"||return 1; S=$(uci_section firewall zone "$Z"); while [ $# -gt 0 ]; do case "$1" in forward_to) zone_fwd to "$Z" "$2"; shift 2; continue;; forward_from) zone_fwd from "$Z" "$2"; shift 2; continue;; esac; uci_parse_kv "$S" "$@"; break; done; }

Is there a way to determine if a variable is a BOOL or a LIST by querying uci ? So that I don't have to maintain this big list of BOOLS and LISTS ?

A lot of the helper functions only exist really to keep the function lenght under 512 chars so that all of this can be used on the fly without the need to create files and such. You can have that entire thing and paste it into console with one rightclick.

Please let me know if you can any bugs !