Skip to content

Repository files navigation

OpenWrt Configurator

OpenWrt Configurator is a CLI tool and corresponding JSON config file which lets you specify the entire state of your network including UCI configuration, packages and firmware versions in a single UCI-like JSON config file which can be provisioned to your OpenWrt devices using OpenWrt Configurator.

$ openwrt-configurator provision ./network-config.json

The JSON config file can be conditionally composed with .if and/or .overrides keys, and implements light abstractions over device ethernet ports and Wi-Fi radios to seamlessly support configuration for multiple devices, different device models/types, as well as different device roles (Router, switch, dump-ap etc) from a single JSON config file.

  "interface": [
    {
      ".if": "device.tag.role == 'router'", // Apply the pppoe interface to only the router.
      ".name": "wan",
      "device": "eth0",
      "proto": "pppoe",
      "username": "me@pppoe.com",
      "password": "123"
    },
    {
      ".name": "lan",
      "device": "br-lan.1",
      ".overrides": [
        {
          ".if": "device.tag.role == 'router'", // Apply a static ip to only the router.
          "override": {
            "proto": "static",
            "ipaddr": "10.0.0.1",
            "netmask": "255.255.0.0"
          }
        },
        {
          ".if": "device.tag.role != 'router'", // Apply dhcp to all non-router devices.
          "override": {
            "proto": "dhcp"
          }
        }
      ]
    },
  ]

Features

  • Store all network config for all devices in a single UCI-like JSON config file (UCI config, packages, firmware versions and more).
  • Provision arbitrary files (e.g. hotplug scripts) for config that UCI can't express, removed from devices when removed from config.
  • Conditionally compose your JSON file to support multiple OpenWrt devices, different device models/types, and different roles (Routers, switches and dump-ap's etc).
  • Light abstractions over ethernet ports and WiFi radios to keep multi-device configuration simple.
  • Strict config syntax validation and logical error checking for configuration to prevent invalid configuration.
  • Convert your JSON file into UCI commands for each of your OpenWrt devices.
  • Provision your JSON file to your OpenWrt devices.
  • JSON file migrations to keep your JSON file up-to-date with any UCI configuration changes/updates.
  • Build and flash sysupgrade images to your OpenWrt devices based on your JSON config file.

Getting started

  1. Download OpenWrt Configurator from the GitHub Releases page.

  2. Download a sample configuration file.

  3. Adjust your configuration file as needed.

  4. Print and inspect your device UCI commands.

$ openwrt-configurator print-uci-commands ./network-config.json
# device my-ap
apk del --rdepends firewall firewall4
uci set system.system0=system
uci set system.system0.hostname='my-ap'
uci set system.system0.timezone='Africa/Johannesburg'
uci set network.switch0=switch
uci set network.switch0.name='switch0'
uci set network.switch0.reset='1'
uci set network.switch0.enable_vlan='1'
...

Note: SSH auth is provisioning_config.ssh_auth (username, optional password and/or private_key_path). Or pass --identity ~/.ssh/id_ed25519 (like ssh -i). With neither password nor key, the SSH agent is used if SSH_AUTH_SOCK is set.

  1. Provision configuration to your devices (Implemented with SSH).
$ openwrt-configurator provision ./network-config.json
Provisioning device "my-ap" @ root@10.0.0.218
Connecting over SSH...
Connected.
Verifying device...
Verified.
Setting configuration...
Configuration set.
Provisioning completed.
...

Note: SSH auth is provisioning_config.ssh_auth (username, optional password and/or private_key_path). Or pass --identity ~/.ssh/id_ed25519 (like ssh -i). With neither password nor key, the SSH agent is used if SSH_AUTH_SOCK is set.

How it works

  1. Add your devices to the JSON config file.
  "devices": [
    {
      "model_id": "ubnt,edgerouter-x",
      "ipaddr": "10.0.0.1",
      "tags": { "role": "router" }, // Give the Edgerouter a tag with role of "router".
      "hostname": "my-router",
      "provisioning_config": {
        "ssh_auth": {
          "username": "root",
          "private_key_path": "~/.ssh/id_ed25519"
        }
      }
    },
    {
      "model_id": "tplink,eap245-v3",
      "ipaddr": "10.0.0.218",
      "tags": { "role": "ap" }, // Give the EAP245 a tag with role of "ap".
      "hostname": "my-ap",
      "provisioning_config": {
        "ssh_auth": {
          "username": "root",
          "password": "123"
        }
      }
    }
  ],
  1. Specify which packages you wanted installed or uninstalled on your devices.
  "package_profiles": [
    {
      ".if": "device.tag.role == 'router'", // Install sqm and https-dns-proxy on the router.
      "packages": [
        "sqm-scripts",
        "luci-app-sqm",
        "https-dns-proxy",
        "luci-app-https-dns-proxy"
      ]
    },
    {
      ".if": "device.tag.role == 'ap'", // Uninstall firewall packages from ap's.
      "packages": ["-firewall", "-firewall4"]
    }
  ],
  1. Optionally provision arbitrary files for configuration that UCI cannot express (e.g. a hotplug script to set a debugfs value). Files are written on provision and tracked in a manifest (/etc/onc/managed_files), so a file removed from your config is removed from the device on the next provision. Use .if to target specific devices.

    Two optional hooks run a command for you, and which one you want depends on what it needs:

    • run_after runs immediately after the file is written, which is before the UCI config is committed and reloaded. Use it when the command only needs the file itself.
    • run_after_reload runs after uci commit and reload_config, once the new config is actually live. Use it when the command depends on something this provision creates — an interface, a bridge, a restarted service. A hotplug script pointed at phy1-ap0 will silently do nothing under run_after if this provision is what creates that interface. provision runs these over a reconnected session, after the device has been confirmed reachable, so they cannot be lost when committing drops the link. If one fails the run fails loudly, but the configuration stays committed and live — only the immediate application of it did not happen.

    Note that files is not the way to create a UCI config: write to /etc/config/<pkg> through the config key instead, or the new config will not be reloaded on the run that creates it.

  "files": [
    {
      ".if": "device.tag.role == 'ap'", // Lower mac80211 AQL on the AP's for lower wifi latency under load.
      "path": "/etc/hotplug.d/net/20-aql",
      "mode": "0755",
      "content": "#!/bin/sh\n[ \"$ACTION\" = \"add\" ] || exit 0\ncase \"$INTERFACE\" in phy*-ap*|wlan*) ;; *) exit 0 ;; esac\nfor phy in /sys/kernel/debug/ieee80211/phy*; do\n  [ -e \"$phy/aql_txq_limit\" ] || continue\n  for ac in 0 1 2 3; do echo \"$ac 3000 6000\" > \"$phy/aql_txq_limit\"; done\ndone\n",
      "run_after_reload": "ACTION=add INTERFACE=phy1-ap0 sh /etc/hotplug.d/net/20-aql" // Apply immediately, without waiting for a reboot. After the reload, so the interface exists.
    }
  ],
  1. Specify your UCI configuration in JSON, and add .if and/or .overrides keys to apply configuration conditionally.
  "config": {
    "dropbear": {
      "dropbear": [
        {
          "PasswordAuth": "on",
          "RootPasswordAuth": "on",
          "Port": 22,
          "BannerFile": "/etc/banner"
        }
      ]
    },
    "system": {
      "system": [
        {
          "timezone": "Africa/Johannesburg"
        }
      ]
    },
    "interface": [
      {
        ".name": "loopback",
        "device": "lo",
        "proto": "static",
        "ipaddr": "127.0.0.1",
        "netmask": "255.0.0.0"
      },
      {
        ".if": "device.tag.role == 'router'", // Apply the pppoe interface to only the router.
        ".name": "wan",
        "device": "eth0",
        "proto": "pppoe",
        "username": "me@pppoe.com",
        "password": "123"
      },
      {
        ".name": "lan",
        "device": "br-lan.1",
        ".overrides": [
          {
            ".if": "device.tag.role == 'router'", // Apply a static ip to only the router.
            "override": {
              "proto": "static",
              "ipaddr": "10.0.0.1",
              "netmask": "255.255.0.0"
            }
          },
          {
            ".if": "device.tag.role != 'router'", // Apply dhcp to all non-router devices.
            "override": {
              "proto": "dhcp"
            }
          }
        ]
      }
    ],
    "wireless": {
      ".if": "device.tag.role == 'ap'", // Applies the entire "wireless" object to only devices with the "ap" tag set.
      "wifi-device": [
        {
          ".name": "radio0",
          "band": "2g"
        },
        {
          ".name": "radio1",
          "band": "5g"
        }
      ],
      "wifi-iface": [
        {
          "mode": "ap",
          "device": ["radio0", "radio1"],
          "network": "lan",
          "ssid": "my-ssid",
          "encryption": "psk2",
          "key": "123456789"
        },
        {
          "mode": "ap",
          "device": ["radio0", "radio1"],
          "network": "guest",
          "ssid": "my-ssid-guest",
          "encryption": "none"
        }
      ]
    }
  }

Roadmap

Short-term

  • Improve README.md and add more documentation.
  • Add more configuration validation and error checking.
  • Improve error handling and messages.
  • Add more sample configurations.
  • Support firmware building and flashing.

Long-term

  • Configuration migrations.
  • Reduce CLI executable size.
  • Web UI for building configuration.

About

A CLI to easily provision UCI configuration to OpenWrt devices.

Topics

Resources

Stars

96 stars

Watchers

6 watching

Forks

Releases

Used by

Contributors

Languages