From 17095aa076a51792cdee916dc433b06bfb936e67 Mon Sep 17 00:00:00 2001 From: Mikael Nordin Date: Thu, 26 Nov 2020 16:28:51 +0100 Subject: [PATCH] Packaging for pip --- README.md | 7 +- build/lib/wireguide/__init__.py | 0 build/scripts-3.8/wireguide | 392 ++++++++++++++++++++++++ dist/wireguide-0.0.1-py3-none-any.whl | Bin 0 -> 18419 bytes dist/wireguide-0.0.1.tar.gz | Bin 0 -> 5871 bytes requirements.txt | 2 + setup.py | 24 ++ wireguide.egg-info/PKG-INFO | 44 +++ wireguide.egg-info/SOURCES.txt | 8 + wireguide.egg-info/dependency_links.txt | 1 + wireguide.egg-info/top_level.txt | 1 + wireguide/__init__.py | 0 logo.png => wireguide/logo.png | Bin wireguide => wireguide/wireguide | 0 14 files changed, 474 insertions(+), 5 deletions(-) create mode 100644 build/lib/wireguide/__init__.py create mode 100755 build/scripts-3.8/wireguide create mode 100644 dist/wireguide-0.0.1-py3-none-any.whl create mode 100644 dist/wireguide-0.0.1.tar.gz create mode 100644 requirements.txt create mode 100644 setup.py create mode 100644 wireguide.egg-info/PKG-INFO create mode 100644 wireguide.egg-info/SOURCES.txt create mode 100644 wireguide.egg-info/dependency_links.txt create mode 100644 wireguide.egg-info/top_level.txt create mode 100644 wireguide/__init__.py rename logo.png => wireguide/logo.png (100%) rename wireguide => wireguide/wireguide (100%) diff --git a/README.md b/README.md index 3d4dbc9..61be961 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,9 @@ For packaging a debian package you can use these dependencies: Thanks to Jan Bodnar of zetcode.com for the valuable tutorial on wxPython dialogs: * http://zetcode.com/wxpython/dialogs/ -## Usage +## Installation ``` -sudo apt get install libnm0 wxpython gir1.2-nm-1.0 -git clone https://github.com/mickenordin/wireguide.git -cd wireguide/ -./wireguide +pip install wireguide ``` ## Screenshots ![No config](https://raw.githubusercontent.com/mickenordin/wireguide/main/screenshots/scrot0.png) diff --git a/build/lib/wireguide/__init__.py b/build/lib/wireguide/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/build/scripts-3.8/wireguide b/build/scripts-3.8/wireguide new file mode 100755 index 0000000..b3c44b0 --- /dev/null +++ b/build/scripts-3.8/wireguide @@ -0,0 +1,392 @@ +#!python +""" +This is a program that can manage Wireguard Configuration graphically +""" +import configparser +import os +import re +import sys +import uuid +from socket import AF_INET + +import gi +import wx +import wx.adv + +gi.require_version("NM", "1.0") +from gi.repository import NM # pylint: disable=wrong-import-position + + +class WireFrame(wx.Frame): # pylint: disable=too-many-ancestors,too-many-instance-attributes + """ + The WireGUIde wx.Frame + """ + def __init__(self, *args, **kw): + super().__init__(*args, **kw) + + self.version = 0.1 + + # Get active conns from NetworkManager + self.client = NM.Client.new(None) + self.conns = self.get_wg_conns() + self.active_conns = self.get_wg_aconns() + + # Set up for loaded configs + self.inactive_conns = get_inactive_conns() + + # create a panel in the frame + self.pnl = wx.Panel(self) + + # and create a sizer to manage the layout of child widgets + self.sizer = wx.BoxSizer(wx.VERTICAL) + self.write_to_sizer() + self.pnl.SetSizer(self.sizer) + + # create a menu bar + self.make_menu_bar() + + # and a status bar + self.statusbar = self.CreateStatusBar(style=wx.BORDER_NONE) + self.set_status() + + self.timer = wx.Timer(self) + self.count = 0 + + self.Bind(wx.EVT_TIMER, self.do_on_timer) + self.Bind(wx.EVT_PAINT, self.timing) + self.Show() + + def about_clicked(self, event): # pylint: disable=unused-argument + """Display an About Dialog""" + about = "WireGUIde is a GUI for WireGuard." + lic_text = """ + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see .""" + #wx.MessageBox(about, "About WireGUIde" ,wx.OK | wx.ICON_INFORMATION) + info = wx.adv.AboutDialogInfo() + info.SetIcon(wx.Icon('logo.png', wx.BITMAP_TYPE_PNG)) + info.SetName('WireGUIde') + info.SetVersion(str(self.version)) + info.SetDescription(about) + info.SetCopyright('(C) 2020 Mikael Nordin') + info.SetWebSite('https://github.com/mickenordin') + info.SetLicence(lic_text) + info.AddDeveloper('Mikael Nordin') + info.AddDocWriter('Mikael Nordin') + info.AddArtist('Mikael Nordin') + + wx.adv.AboutBox(info) + + def activate_button_clicked(self, event, conn): # pylint: disable=unused-argument + """ + This activates an imported config + """ + print(conn.get_id()) + self.client.add_connection_async(conn, False, None, self.add_callback, + None) + self.remove_inactive(conn) + + def add_callback(self, client, result, data): # pylint: disable=unused-argument + """ + This is here to let us know if the connection was successful or not + """ + try: + client.add_connection_finish(result) + print( + "The connection profile has been successfully added to NetworkManager." + ) + except Exception as exception: # pylint: disable=broad-except + sys.stderr.write("Error: %s\n" % exception) + self.active_conns = self.get_wg_aconns() + self.conns = self.get_wg_conns() + self.write_to_sizer() + + def create_conn_from_file(self, pathname): + """ + Read a WireGuardUI config file and convert it in to + an object that can be user by NetworkManager + """ + filename = os.path.basename(pathname) + try: + config = configparser.ConfigParser() + config.read(pathname) + iname = self.get_next_int_name() + profile = NM.SimpleConnection.new() + s_con = NM.SettingConnection.new() + s_con.set_property(NM.SETTING_CONNECTION_ID, iname) + s_con.set_property(NM.SETTING_CONNECTION_INTERFACE_NAME, iname) + s_con.set_property(NM.SETTING_CONNECTION_UUID, str(uuid.uuid4())) + s_con.set_property(NM.SETTING_CONNECTION_TYPE, + NM.SETTING_WIREGUARD_SETTING_NAME) + + s_wireguard = NM.SettingWireGuard.new() + s_wireguard.set_property(NM.SETTING_WIREGUARD_PRIVATE_KEY, + config['Interface']['PrivateKey']) + s_peer = NM.WireGuardPeer.new() + s_peer.set_endpoint(config['Peer']['Endpoint'], False) + s_peer.set_public_key(config['Peer']['PublicKey'], False) + s_peer.append_allowed_ip(config['Peer']['AllowedIPs'], False) + s_wireguard.append_peer(s_peer) + + s_ip4 = NM.SettingIP4Config.new() + s_ip4_address = NM.IPAddress(AF_INET, + config['Interface']['Address'], + int(32)) + s_ip4.set_property(NM.SETTING_IP_CONFIG_METHOD, "manual") + s_ip4.add_address(s_ip4_address) + s_ip4.add_dns(config['Interface']['DNS']) + + profile.add_setting(s_con) + profile.add_setting(s_ip4) + profile.add_setting(s_wireguard) + return profile + + except IOError: + wx.LogError("Cannot open file '%s'." % filename) + return None + + def deactivate_button_clicked(self, event, conn): # pylint: disable=unused-argument + """ + This deactivates an active config + """ + print(conn.get_id()) + self.client.deactivate_connection_async(conn, None, self.de_callback, + conn) + conn.get_connection().delete_async(None, None, None) + + def de_callback(self, client, result, data): # pylint: disable=unused-argument + """ + This is here to let us know if the deactivation was successful or not + """ + try: + client.deactivate_connection_finish(result) + print( + "The connection profile has been successfully removed from NetworkManager." + ) + except Exception as exception: # pylint: disable=broad-except + sys.stderr.write("Error: %s\n" % exception) + self.active_conns = self.get_wg_aconns() + self.conns = self.get_wg_conns() + self.write_to_sizer() + + def do_on_timer(self, event): # pylint: disable=unused-argument + """ + Do stuff to redraw the window when the timer times out + Ths is because connections might change outside of WireGUIde + """ + self.count += 1 + if self.count == 5: + self.client.reload_connections_async() + self.active_conns = self.get_wg_aconns() + self.conns = self.get_wg_conns() + self.set_status() + self.write_to_sizer() + self.count = 0 + + def exit_clicked(self, event): # pylint: disable=unused-argument + """ + Close the frame, terminating the application. + """ + self.Close(True) + + def file_chooser_clicked(self, event): # pylint: disable=unused-argument + """ + This is what happens when you click on the file chooser + """ + with wx.FileDialog(self, + "Open WireGuard config file", + wildcard="WireGuard files (*.conf)|*.conf", + style=wx.FD_OPEN + | wx.FD_FILE_MUST_EXIST) as file_dialog: + + if file_dialog.ShowModal() == wx.ID_CANCEL: + return # the user changed their mind + + # Proceed loading the file chosen by the user + pathname = file_dialog.GetPath() + new_conn = self.create_conn_from_file(pathname) + self.inactive_conns.append(new_conn) + self.write_to_sizer() + + def get_next_int_name(self): + """ + Determins what we should call the next interface + """ + temp = [] + for conn in self.conns: + temp.append(re.findall(r'\d+', conn.get_interface_name())) + for conn in self.inactive_conns: + temp.append(re.findall(r'\d+', conn.get_interface_name())) + numbers = [int(item) for sublist in temp for item in sublist] + if not numbers: + num = 0 + else: + numbers.sort(reverse=True) + num = numbers[0] + 1 + return "wg" + str(num) + + def get_wg_aconns(self): + """ + Reads all active wireguard connections from NetworkManager + and returns them as objects in an array + """ + mconns = [] + wgconns = self.client.get_active_connections() + for conn in wgconns: + if conn.get_connection_type() == NM.SETTING_WIREGUARD_SETTING_NAME: + mconns.append(conn) + return mconns + + def get_wg_conns(self): + """ + Reads all current wireguard connections from NetworkManager + and returns them as objects in an array + """ + mconns = [] + wgconns = self.client.get_connections() + for conn in wgconns: + if conn.get_connection_type() == NM.SETTING_WIREGUARD_SETTING_NAME: + mconns.append(conn) + return mconns + + def make_menu_bar(self): + """ + A menu bar is composed of menus, which are composed of menu items. + This method builds a set of menus and binds handlers to be called + when the menu item is selected. + """ + + file_menu = wx.Menu() + file_chooser_item = file_menu.Append(-1, "&Open...\tCtrl-O", + "Select WireGuard config file") + file_menu.AppendSeparator() + exit_item = file_menu.Append(wx.ID_EXIT) + + help_menu = wx.Menu() + about_item = help_menu.Append(wx.ID_ABOUT) + + menu_bar = wx.MenuBar() + menu_bar.Append(file_menu, "&File") + menu_bar.Append(help_menu, "&Help") + + self.SetMenuBar(menu_bar) + + self.Bind(wx.EVT_MENU, self.file_chooser_clicked, file_chooser_item) + self.Bind(wx.EVT_MENU, self.exit_clicked, exit_item) + self.Bind(wx.EVT_MENU, self.about_clicked, about_item) + + def remove_inactive(self, conn): + """ + Remove the inactive connection from the array + """ + for i in range(len(self.inactive_conns)): + if self.inactive_conns[i].get_id() == conn.get_id(): + self.inactive_conns.pop(i) + + def set_status(self): + """ + Update the status bar + """ + status = str(len(self.active_conns)) + " active connection(s)" + self.statusbar.SetStatusText(status) + + def timing(self, event): # pylint: disable=unused-argument + """ + Start a timer + """ + self.timer.Start(20) + + def write_to_sizer(self): + """ + We use the BoxSizer to hold our configs + This method redraws the sizer + """ + self.sizer.Clear(delete_windows=True) + if len(self.active_conns) > 0: + hd1 = wx.StaticText(self.pnl) + hd1.SetLabelMarkup("Active connections") + self.sizer.Add(hd1, 0, wx.ALIGN_CENTER) + for conn in self.active_conns: + statstr = wx.StaticText(self.pnl) + info = get_info_as_text(conn) + statstr.SetLabelMarkup(info) + self.sizer.Add(statstr, + wx.SizerFlags().Border(wx.TOP | wx.LEFT, 25)) + dbtn = wx.Button(self.pnl, -1, "Deactivate") + self.sizer.Add(dbtn, 0, wx.ALIGN_CENTER) + self.Bind(wx.EVT_BUTTON, + lambda event, mconn=conn: self. + deactivate_button_clicked(event, mconn), + dbtn) + if len(self.inactive_conns) > 0: + hd2 = wx.StaticText(self.pnl) + hd2.SetLabelMarkup("Imported configs") + self.sizer.Add(hd2, 0, wx.ALIGN_CENTER) + if self.inactive_conns: + for conn in self.inactive_conns: + statstr = wx.StaticText(self.pnl) + info = get_info_as_text(conn) + statstr.SetLabelMarkup(info) + self.sizer.Add( + statstr, + wx.SizerFlags().Border(wx.TOP | wx.LEFT, 25)) + btn = wx.Button(self.pnl, -1, "Activate") + self.sizer.Add(btn, 0, wx.ALIGN_CENTER) + self.Bind(wx.EVT_BUTTON, + lambda event, mconn=conn: self. + activate_button_clicked(event, mconn), + btn) + if (len(self.active_conns) == 0) and (len(self.inactive_conns) == 0): + hd0 = wx.StaticText(self.pnl) + hd0.SetLabelMarkup("No configs available") + missingstr = wx.StaticText(self.pnl) + missingstr.SetLabelMarkup( + "Please choose a config file from the file menu (CTRL+O).") + self.sizer.Add(hd0, 0, wx.ALIGN_CENTER) + self.sizer.Add(missingstr, 0, wx.ALIGN_LEFT) + self.sizer.Layout() + + +def get_inactive_conns(): + """ + TODO: Not implemented yet + """ + minactive_conns = [] + #return empty array for now, we cant read configs from stash yet + return minactive_conns + + +def get_info_as_text(aconn): + """ + Returns info about a connection as text + """ + try: + conn = aconn.get_connection() + except Exception: # pylint: disable=broad-except + conn = aconn + mid = conn.get_id() + miname = conn.get_interface_name() + muuid = conn.get_uuid() + info = "id: " + mid + '\n' + info += "interface name: " + miname + '\n' + info += "uuid: " + muuid + '\n' + return info + + +if __name__ == '__main__': + # When this module is run (not imported) then create the app, the + # frame, show it, and start the event loop. + app = wx.App() + frm = WireFrame(None, title='WireGUIde') + frm.Show() + app.MainLoop() diff --git a/dist/wireguide-0.0.1-py3-none-any.whl b/dist/wireguide-0.0.1-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..d95d76fe881da82db84d0a26c37998ab79a3fab8 GIT binary patch literal 18419 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.6 +Description-Content-Type: text/markdown diff --git a/wireguide.egg-info/SOURCES.txt b/wireguide.egg-info/SOURCES.txt new file mode 100644 index 0000000..cfe19e8 --- /dev/null +++ b/wireguide.egg-info/SOURCES.txt @@ -0,0 +1,8 @@ +README.md +setup.py +wireguide/__init__.py +wireguide/wireguide +wireguide.egg-info/PKG-INFO +wireguide.egg-info/SOURCES.txt +wireguide.egg-info/dependency_links.txt +wireguide.egg-info/top_level.txt \ No newline at end of file diff --git a/wireguide.egg-info/dependency_links.txt b/wireguide.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/wireguide.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/wireguide.egg-info/top_level.txt b/wireguide.egg-info/top_level.txt new file mode 100644 index 0000000..a2cdbed --- /dev/null +++ b/wireguide.egg-info/top_level.txt @@ -0,0 +1 @@ +wireguide diff --git a/wireguide/__init__.py b/wireguide/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/logo.png b/wireguide/logo.png similarity index 100% rename from logo.png rename to wireguide/logo.png diff --git a/wireguide b/wireguide/wireguide similarity index 100% rename from wireguide rename to wireguide/wireguide