This adds an OpenVPN Status Feature Wizard to your EdgeOS router in the Wizards tab. It does this by parsing and displaying the log file generated by OpenVPN with the --status
option.
DISCLAIMER: This is a personal utility I made for myself and comes with no guarantees that it will work on every (or any) EdgeMAX router. I did not have any documentation or support to build this Wizard. I simply reverse-engineered the "VPN status" wizard that comes with the unit to create this. This has only been tested on an EdgeRouter X running EdgeOS 1.10.11
and 2.0.8
. It should be considered beta and not ready for a production environment. Install, tinker and improve at your own risk.
OpenVPN must be configured to generate a status log using the --status
option.
Recommendations:
- Generate the log under the
/config/
path. This folder tree survives firmware upgrades. - Specify an absolute path to the log so you can be sure it's where you expect it to be.
- You could also use the admin users home folder, e.g.
/home/ubnt/
as the log location if you prefer.
configure
set interfaces openvpn vtun0 openvpn-option "--status /config/openvpn/status.log"
commit;save
exit
Note: If you had previously set a --status
path in your openvpn configuration, remove that old path.
The next step is to install the Wizard on the router. Note: In the admin interface there is a +
button beside the Feature wizards section, but I have not yet figured out what format it expects the files in (zip or individual file uploads did not work for me.) If somebody knows how to package files for this upload, I'd love to know.
For the wizard to work, its folder needs to end up in /config/wizard/feature
. To get it there I copied the files to the router via scp
to the admin user's home folder (replace your server's login credentials & IP as needed):
$ scp -r OpenVPN_status/ [email protected]:~/
Then I logged into the router, changed to root, moved the folder, and set permissions:
$ sudo su
# cp -a /home/ubnt/OpenVPN_status/ /config/wizard/feature/
# chown -R www-data:vyattacfg /config/wizard/feature/OpenVPN_status
# chmod +x /config/wizard/feature/OpenVPN_status/wizard-run
(Thanks to @r2munz for reminding me about the chmod +x
;)
The script is setup by default to look for the status file in /config/openvpn/status.log
. If your path is different, edit the wizard-run
script and change the STATUS_LOG_PATH
:
sudo su
vi /config/wizard/feature/OpenVPN_status/wizard-run
You'll find it up top:
STATUS_LOG_PATH="/config/openvpn/status.log"
Login to your router and go to the Wizards area. You should see an OpenVPN status link under the Features area. Clicking on it should bring up a parsed version of the OpenVPN status log with two tables: the list of clients and the routing information below that. If this information comes up, you're done!
When I noticed the +
button beside Features in the Wizards area, I realized the intention was for the community to build their own wizards for the router. Of course my first instinct was to look for a repository of community (or official) wizards I could add. All I could find however were users asking if something like this exists in the Ubiquiti support forums. Apparently there is a beta testing program where some users have gotten acccess to preliminary docs / info on this, but it's not public yet.
The router runs on Debian and this is a web interface. My first instinct was to see if anything lived in the /var/www
folder, and viola, inside of it was a folder called wizard
that contained status
and feature
sub-folders. I originally built my wizard and placed it under this structure. It worked fine... except when I updated from 1.10.11
to 2.0.8
, my wizard was wiped out. Looks like these are system folders which are reset on firmware updates/changes and can't be used. I later discovered the /config/wizard/feature
folder which I know will persist across firmware updates.
Diving into the VPN_status
folder to learn how they work, I was surprised to see how simple the structure of a Feature Wizard was. The system is very much designed around convention over configuration. You simply put things where they should be and name them a certain way, and the magic fairies will make the rest work. Systems like this can be easy and quick to set up, but can also be frustrating & limiting if you don't know how the system really works or what the available options are.
Each system wizard lives in its own folder under /var/www/wizard/feature
The folder name of the wizard becomes its title in the menu. Underscores are converted to spaces. For example, /var/www/wizard/feature/VPN_status
becomes VPN status
in the web UI.
Each wizard folder has two files in it:
wizard-run # bash script to process command
wizard.html # html template
Note: For wizards that take input, there is also a validator.json
file with validation rules for the input. I won't cover this here since I haven't played with it yet. This feature wizard also doesn't use it.
The wizard-run
file is nothing more than a bash script that takes a command and some input, and replies with a JSON response. Treat it as a bash-driven JSON API.
The wizard.html
file is the view/template for the wizard. It takes input and displays output, and has a specific HTML structure that EdgeOS's javascript depends on.
wizard-run
takes two command-line input parameters:
$1
is the command/action the wizard should perform. This is a string, and the first command a wizard gets from EdgeOS seems to beload
, which asks it to load any initial information required to be displayed.$2
is any info submitted from the wizard by the user (via form submission). This is not used during the initialload
command. Only wizards that take input use this. Since this wizard doesn't and I haven't played with this functionality, I won't comment on it here. (But looking at other wizards, and given the pattern across EdgeOS, I believe this parameter will receive a JSON object representing the form submission, which you'd usejq
to parse. TheDNS_host_names
wizard seems to follow this pattern.)
Beyond this, it's nothing more than a regular bash script. You can run any available bash commands on the system, use tools like sed
, grep
, awk
, etc. You can also use some API functions exposed via the command-line by EdgeOS / Vyatta.
Whatever you do with it, the script must then return a JSON object in this basic format:
{
success: 1 | 0,
data: {
key: ...,
key2: ...,
},
readonly: 1 | 0
}
success
should be 1
to indicate success of the query, or 0
for failure.
data
is an object with named properties. The names of the properties are used to bind and display data in wizard.html
readonly
seems to indicate whether the data returned should be rendered as read-only or not. Our script always returns 1
as it is read-only. See the wizard.html
info below for more details.
This is the display template for the interface. I don't know a lot about how EdgeOS is built, but I would venture to guess this interface utilizes existing code/conventions used across other core EdgeOS screens.
It seems to employ a rudimentary Javascript data-binding system, where a JSON object is used to automatically populate/manipulate the HTML in the template. The HTML must be named/structure a certain way for this to work. You'll note there's no JavaScript in this file or in any of the Wizard folders. The JavaScript that runs this is elsewhere, and I'm guessing it's more core to EdgeOS as a whole and it's probably not meant to be changed.
The key things in the template which seem to affect how it runs are:
- The
div.addable
element contains adata-object
attribute, and that value must match one of the keys for yourdata
property in the JSON response. For example:
<div class="addable" data-object="clients" data-objectify="1">
The clients
value tells the system to use data.clients
in the JSON response from wizard-run
:
{
success: 1 | 0,
data: {
clients: [
{ ... },
{ ... },
],
routing: [
{ ... },
{ ... },
]
},
readonly: 1 | 0
}
- Inside of this element, is another
div.addable-template
element. The HTML inside of this element seems to be the template for rendering each record returned in the specified key in the JSON object (styles removed for clarity):
<div class="addable-template">
<tr>
<td><input type="text" disabled class="hostname" name="name" style="..."></td>
<td><input type="text" disabled class="hostname" name="address" style="..."></td>
<td><input type="text" disabled class="hostname" name="received" style="..."></td>
<td><input type="text" disabled class="hostname" name="sent" style="..."></td>
<td><input type="text" disabled class="hostname" name="connected" style="..."></td>
</tr>
</div>
The key attribute here is name
. The value of name
sets which property in the JSON data object to populate the field with. In this wizard, we send back a data object that looks like this:
...
data: {
clients: [
{ name: "client1", address: "x.x.x.x", received: "12345", sent: "12345", connected: "date" },
{ name: "client2", address: "x.x.x.x", received: "12345", sent: "12345", connected: "date" }
],
...
}
If the name
attribute matches the name of the property in the JSON response object, the system will automatically set the text field to that value.
- The
table.addable-container
element is where the rows are rendered using the template above. In our wizard.html, this element looks like so:
<table class="addable-container">
<tr><th>Name</th><th style="width:130px">Address</th><th style="width:80px">Bytes in</th><th style="width:80px">Bytes out</th><th style="width:145px">Connected</th></tr>
</table>
It keeps the header row and appends the rows using the template below it (e.g. like jQuery's append()
method.)
That's the basic structure of wizard.html. I have not extensively experimented with it, but I believe you can change from a <table>
element to others using the same structure. The class names dictate what's what.
One thing I have not figured out, however, is how to get the system to render data into elements other than an input
field with a name
attribute. In my wizard I simply added some styles to make it look less like an input element, but there may be some mobile/usability issues with doing this.
Of course, if you're part of the beta program maybe there's a whole document about all of this and I'm wasting my time, but this is what I've discovered, and I hope it helps others.
- Error handling - what if the status file is not where the script thinks it is? What if no users are connected? OpenVPN not setup? etc.
- Parsing - there's probably better ways to parse the file. I'm not a bash parsing expert.
- Output - there might be cleaner ways to output this information. Might be limited by the Wizard conventions.
- Date updated - also output the date last updated (for the file overall), found at the top of the status file.