Dialog based sample tool for configuring network interfaces with pythondialog and debinterface
So, what do you do when you want to give out a Linux based virtual machine image to someone and want a cool config tool which allows non-seasoned console Jockeys (regular users) to easily configure a static ip address for their network interface?
We know, use a docker container. And we will. That is the best option, period. But a docker container probably also needs a static ip change by the user (or not, but we have not much docker experience yet. This is still a todo).
You could ask anyone in attached documentation to modify a couple of lines in etc/network/interfaces
, but hey, we can do much
better than that. First, we ship console only Ubuntu Servers (no gui), and sometimes it is a pain to let someone without enough
Linux skills to hack around with vim and the interfaces file. Well. Let’s create a dialog based config tool!
Dialog is a great tool for this, it is based on the ncurses library, dating all the way back to 1993 ;). Great tools are ageless. Originally it is a simple command line program, making it very easy to include into shell scripts. You can display full screen message boxes, selection dialogs, forms, tree views, tail log displays, and much more. However, if you are like us, you also don’t really want to write anything longer than 5-10 lines in bash. Python is the way to go!
Fortunately, there is a wrapper above the dialog
program, called pythondialog. It is available for both python3 and
python2.7. Code can be much more pythonic this way, you don’t need to call subprocess.Popen
all the time for your dialog code (the lib will call it for you :)). Check out the documentation.
Okay, what do we want to achieve?
- The user should type simply
sudo run-config.py
, and let the dialogs drive the configuration process. - Display a list of available network interfaces to chose from
- Select either dhcp or static address configuration for the selected interface.
- If the user selects static configuration, display a form where ipv4 address, netmask and gateway settings could be entered.
- Handle input errors
- Write or update the selected network interface configuration data into
/etc/network/interfaces
file - Restart the interface to apply changes
As you can see, this is a real life example. We like real life examples. Hope you do as well. One thing which could be boring is to
figure out ways to correctly parse (and write) the /etc/network/interfaces
file for a specific adapter, without overwriting the rest of the file. This could be quite boring when your main task is about to drive dialogs. Correctly parsing the file is just an obstacle, yet it must be
done correctly.
Take a good look around when you have situations like this. The Python community is big. We could not image this is was not solved by somebody earlier. Turns out we were right, here it is, it is called debinterface. Great, now we can simply depend on this library, instead of trying to write clueless print statements to our interfaces file (Note: the import statements, as documented in debinterface’s README.md, did not work for us. Check our scripts for the correct import).
In order to deploy this script to your Linux server, you should:
sudo apt-get install python-pip
sudo apt-get install dialog
sudo pip install python2-pythondialog
- clone debinterface repository, and copy the contents to
~/.local/lib/python2.7/site-packages
(Note your local site package folder may be different. Check the output ofpython -m site --user-site
to see where is yours.
Now, here is the script. Don’t forget to mark it as executable with chmod +x pydialog-interfaces.py
and run with sudo
. Also available on
GitHub.
What else? Screenshots:
And the script itself:
#!/usr/bin/env python
from __future__ import print_function
import locale
import os
import re
import sys
import subprocess
from dialog import Dialog
from debinterface.interfaces import Interfaces
class Iface:
def __init__(self, name, description):
self.name = name
self.description = description
class Constants:
DHCP = 'dhcp'
STATIC = 'static'
TXT_BACKGROUND_TITLE = 'Network Interface Configuration'
TXT_ERR_ROOT_REQUIRED = 'root privileges required. run with sudo'
TXT_NETWORK_CFG_SUCCESS = 'Network configuration completed successfully!\n\n'
TXT_NETWORK_CFG_ERROR = 'Error occured while configuring network interface!\n\n'
TXT_WELCOME_TITLE = 'Welcome to pydialog-interfaces configuration!\n\nThis tool helps you to set up your network interface.'
TXT_SELECT_INTERFACE = 'Select interface'
TXT_SELECT_SOURCE = 'Select address source'
TXT_MESSAGE_DHCP = 'Configuring for DHCP provided address...'
TXT_MESSAGE_STATIC = 'Configuring for static IP address...'
TXT_MESSAGE_ERROR = '\Zb\Z1Error: %s\n\n\Z0Please try again.'
TXT_CONFIG_STATIC_TITLE = 'Provide the values for static IP configuration'
def clearquit():
os.system('clear')
sys.exit(0)
def run_process(command, stderr=False):
p = subprocess.Popen([command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
output = ""
while(True):
retcode = p.poll()
if stderr:
line = p.stderr.readline().decode('utf-8')
else:
line = p.stdout.readline().decode('utf-8')
output = output + line
if retcode is not None:
break
return {'retcode': retcode, 'output': output}
def network_restart(selected_iface):
# check if iface is up
results = run_process("ifconfig | grep " + selected_iface, stderr=False)
selected_iface_is_down = True
for line in results['output'].splitlines():
if re.match(selected_iface, line):
selected_iface_is_down = False
if selected_iface_is_down:
command = "ifup " + selected_iface
else:
command = "ifdown " + selected_iface + " && ifup " + selected_iface
return run_process(command, stderr=True)
def write_and_display_results(interfaces, selected_iface):
interfaces.writeInterfaces()
results = network_restart(selected_iface)
if results['retcode'] == 0:
text = Constants.TXT_NETWORK_CFG_SUCCESS
else:
text = Constants.TXT_NETWORK_CFG_ERROR
code = d.msgbox(text + results['output'])
clearquit()
# some sanity checks here, sudo only
locale.setlocale(locale.LC_ALL, '')
if os.getuid() != 0:
print(Constants.TXT_ERR_ROOT_REQUIRED)
sys.exit(1)
# display available interfaces to configure
interfaces = Interfaces()
d = Dialog(dialog='dialog', autowidgetsize=True)
d.set_background_title(Constants.TXT_BACKGROUND_TITLE)
code = d.yesno(Constants.TXT_WELCOME_TITLE,
height="15", width=65, yes_label="OK", no_label="Cancel")
if (code == Dialog.CANCEL or code == Dialog.ESC):
clearquit()
available_adapters = []
available_iface_list = run_process("ifconfig -a | grep eth")['output']
for line in available_iface_list.splitlines():
elems = line.split(' ', 1)
iface = Iface(elems[0].strip(), elems[1].strip())
available_adapters.append(iface)
choices = []
for adapter in available_adapters:
choices.append((adapter.name, adapter.description))
code, tag = d.menu(Constants.TXT_SELECT_INTERFACE, choices=choices)
if code == Dialog.OK:
selected_iface = tag
# check if selected_iface is already listed or not in interfaces file using debinterfaces
configured_iface = None
for adapter in interfaces.adapters:
item = adapter.export()
if item['name'] == selected_iface:
configured_iface = item
break
# remove from adapter list if it is already configured
if configured_iface is not None:
interfaces.removeAdapterByName(selected_iface)
code, tag = d.menu(Constants.TXT_SELECT_SOURCE, choices=[(Constants.DHCP, 'Dynamic IP'), (Constants.STATIC, 'Static IP')])
if code == Dialog.OK:
if tag == Constants.DHCP:
code = d.infobox(Constants.TXT_MESSAGE_DHCP)
# simply add
interfaces.addAdapter({
'name': selected_iface,
'auto': True,
'addrFam': 'inet',
'source': Constants.DHCP}, 0)
write_and_display_results(interfaces, selected_iface)
if tag == Constants.STATIC:
while True:
try:
code, values = d.form(Constants.TXT_CONFIG_STATIC_TITLE, [
# title, row_1, column_1, field, row_1, column_20, field_length, input_length
('IP Address', 1, 1, '192.168.0.10', 1, 20, 15, 15),
# title, row_2, column_1, field, row_2, column_20, field_length, input_length
('Netmask', 2, 1, '255.255.255.0', 2, 20, 15, 15),
# title, row_3, column_1, field, row_3, column_20, field_length, input_length
('Gateway', 3, 1, '192.168.0.1', 3, 20, 15, 15)
], width=70)
if (code == Dialog.CANCEL or code == Dialog.ESC):
clearquit()
code = d.infobox(Constants.TXT_MESSAGE_STATIC)
# simply add
interfaces.addAdapter({
'name': selected_iface,
'auto': True,
'addrFam': 'inet',
'source': Constants.STATIC,
'address': values[0],
'netmask': values[1],
'gateway': values[2]}, 0)
write_and_display_results(interfaces, selected_iface)
except Exception, e:
code = d.msgbox(text=Constants.TXT_MESSAGE_ERROR % e, colors=True)
else:
clearquit()
else:
clearquit()