diff --git a/dangerzone/gui/__init__.py b/dangerzone/gui/__init__.py index 0f4df00..10fd44f 100644 --- a/dangerzone/gui/__init__.py +++ b/dangerzone/gui/__init__.py @@ -8,6 +8,7 @@ from PySide2 import QtCore, QtWidgets from .common import GuiCommon from .main_window import MainWindow +from .vm import Vm from .systray import SysTray from .docker_installer import ( is_docker_installed, @@ -101,6 +102,17 @@ def gui_main(custom_container, filename): docker_installer.start() return + # The dangerzone VM, for non-Linux platforms + if platform.system() == "Darwin": + vm = Vm(global_common) + else: + vm = None + + # Create the system tray + systray = SysTray(global_common, gui_common, app, vm) + if vm: + vm.start() + closed_windows = {} windows = {} @@ -156,7 +168,4 @@ def gui_main(custom_container, filename): # If the application is activated and all windows are closed, open a new one app_wrapper.application_activated.connect(application_activated) - # Create a system tray, which also handles the VM subprocess - systray = SysTray(global_common, gui_common, app) - sys.exit(app.exec_()) diff --git a/dangerzone/gui/systray.py b/dangerzone/gui/systray.py index 4b71030..e7495c1 100644 --- a/dangerzone/gui/systray.py +++ b/dangerzone/gui/systray.py @@ -1,59 +1,51 @@ -import os +import platform from PySide2 import QtWidgets class SysTray(QtWidgets.QSystemTrayIcon): - def __init__(self, global_common, gui_common, app): + def __init__(self, global_common, gui_common, app, vm): super(SysTray, self).__init__() self.global_common = global_common self.gui_common = gui_common self.app = app + self.vm = vm self.setIcon(self.gui_common.get_window_icon()) menu = QtWidgets.QMenu() - self.status_action = menu.addAction("...") - self.status_action.setEnabled(False) - menu.addSeparator() - self.restart_action = menu.addAction("Restart") - self.restart_action.triggered.connect(self.restart_clicked) + + if platform.system() == "Darwin": + self.status_action = menu.addAction("...") + self.status_action.setEnabled(False) + menu.addSeparator() + self.restart_action = menu.addAction("Restart") + self.restart_action.triggered.connect(self.restart_clicked) + self.quit_action = menu.addAction("Quit") self.quit_action.triggered.connect(self.quit_clicked) self.setContextMenu(menu) self.show() - # Dangerzone VM - self.vpnkit_p = None - self.hyperkit_p = None - self.hyperkit_path = self.global_common.get_resource_path("bin/hyperkit") - self.vpnkit_path = self.global_common.get_resource_path("bin/vpnkit") - self.vm_iso_path = self.global_common.get_resource_path( - "vm/alpine-dangerzone-v3.14-x86_64.iso" - ) - self.vm_kernel_path = self.global_common.get_resource_path("vm/vmlinuz-virt") - self.vm_initramfs_path = self.global_common.get_resource_path( - "vm/initramfs-virt" - ) - self.vm_state_dir = os.path.join(self.global_common.appdata_path, "vm-state") - os.makedirs(self.vm_state_dir, exist_ok=True) - self.vm_start() + if self.vm: + self.vm.vm_state_change.connect(self.vm_state_change) - def vm_start(self): - self.status_action.setText("Starting Dangerzone ...") - - # Kill existing processes - if self.vpnkit_p is not None: - self.vpnkit_p.terminate() - if self.hyperkit_p is not None: - self.hyperkit_p.terminate() - - # Run VPNKit - - # Run Hyperkit + def vm_state_change(self, state): + if state == self.vm.STATE_OFF: + self.status_action.setText("Dangerzone VM is off") + self.restart_action.setEnabled(True) + elif state == self.vm.STATE_STARTING: + self.status_action.setText("Dangerzone VM is starting...") + self.restart_action.setEnabled(False) + elif state == self.vm.STATE_ON: + self.status_action.setText("Dangerzone VM is running") + self.restart_action.setEnabled(True) + elif state == self.vm.STATE_STOPPING: + self.status_action.setText("Dangerzone VM is stopping...") + self.restart_action.setEnabled(False) def restart_clicked(self): - self.status_action.setText("Restarting Dangerzone ...") + self.vm.restart() def quit_clicked(self): self.app.quit() diff --git a/dangerzone/gui/vm.py b/dangerzone/gui/vm.py new file mode 100644 index 0000000..03db391 --- /dev/null +++ b/dangerzone/gui/vm.py @@ -0,0 +1,91 @@ +import os +import sys +import subprocess +import uuid +import pipes +from PySide2 import QtCore + + +class Vm(QtCore.QObject): + STATE_OFF = 0 + STATE_STARTING = 1 + STATE_ON = 2 + STATE_STOPPING = 3 + + vm_state_change = QtCore.Signal(int) + + def __init__(self, global_common): + super(Vm, self).__init__() + self.global_common = global_common + + # VM starts off + self.state = self.STATE_OFF + + # Hyperkit subprocess + self.hyperkit_p = None + + # Relevant paths + self.hyperkit_path = self.global_common.get_resource_path("bin/hyperkit") + self.vm_iso_path = self.global_common.get_resource_path("vm/dangerzone.iso") + self.vm_kernel_path = self.global_common.get_resource_path("vm/kernel") + self.vm_initramfs_path = self.global_common.get_resource_path( + "vm/initramfs.img" + ) + + # Folder to hold files related to the VM + self.vm_state_dir = os.path.join(self.global_common.appdata_path, "vm-state") + os.makedirs(self.vm_state_dir, exist_ok=True) + + # UDID for VM + self.vm_uuid = str(uuid.uuid4()) + self.vm_cmdline = "modules=virtio_net console=ttyS0" + + def start(self): + self.state = self.STATE_STARTING + self.vm_state_change.emit(self.state) + + # Kill existing process + if self.hyperkit_p is not None: + self.hyperkit_p.terminate() + self.hyperkit_p = None + + # Run Hyperkit + args = [ + self.hyperkit_path, + "-F", + os.path.join(self.vm_state_dir, "hyperkit.pid"), + "-A", + "-u", + "-m", + "4G", + "-c", + "2", + "-s", + "0:0,hostbridge", + "-s", + "31,lpc", + "-l", + "com1,stdio", + "-s", + f"1:0,ahci-cd,{self.vm_iso_path}", + "-s", + "2:0,virtio-net", + "-U", + self.vm_uuid, + "-f", + f'kexec,{self.vm_kernel_path},{self.vm_initramfs_path},"{self.vm_cmdline}"', + ] + args_str = " ".join(pipes.quote(s) for s in args) + print("> " + args_str) + + self.hyperkit_p = subprocess.Popen( + args, + stdout=sys.stdout, + stderr=sys.stderr, + ) + + def restart(self): + pass + + def stop(self): + pass diff --git a/install/vm-builder/build-iso.sh b/install/vm-builder/build-iso.sh index 984ed89..40d7295 100755 --- a/install/vm-builder/build-iso.sh +++ b/install/vm-builder/build-iso.sh @@ -38,6 +38,7 @@ sudo -u user sh mkimage.sh --tag v3.14 \ --repository http://dl-cdn.alpinelinux.org/alpine/v3.14/main \ --repository http://dl-cdn.alpinelinux.org/alpine/v3.14/community \ --profile dangerzone +mv alpine-dangerzone-v3.14-x86_64.iso dangerzone.iso # Fix permissions chmod 755 /vagrant/vm @@ -45,7 +46,9 @@ chmod 644 /vagrant/vm/* # Extract vmlinuz and initramfs cd /vagrant/vm -7z x alpine-dangerzone-v3.14-x86_64.iso boot/vmlinuz-virt -7z x alpine-dangerzone-v3.14-x86_64.iso boot/initramfs-virt +7z x dangerzone.iso boot/vmlinuz-virt +7z x dangerzone.iso boot/initramfs-virt mv boot/* . rm -r boot +mv vmlinuz-virt kernel +mv initramfs-virt initramfs.img diff --git a/install/vm-builder/run-vm.sh b/install/vm-builder/run-vm.sh index 27611ce..b55ba7b 100755 --- a/install/vm-builder/run-vm.sh +++ b/install/vm-builder/run-vm.sh @@ -22,10 +22,10 @@ $HYPERKIT \ -c 2 \ -s 0:0,hostbridge -s 31,lpc \ -l com1,stdio \ - -s 1:0,ahci-cd,$ROOT/alpine-dangerzone-v3.14-x86_64.iso \ + -s 1:0,ahci-cd,$ROOT/dangerzone.iso \ -s 2:0,virtio-net \ -U 9efa82d7-ebd5-4287-b1cc-ac4160a39fa7 \ - -f kexec,$ROOT/vmlinuz-virt,$ROOT/initramfs-virt,"earlyprintk=serial console=ttyS0 modules=loop,squashfs,sd-mod,usb-storage vpnkit.connect=connect://2/1999" + -f kexec,$ROOT/kernel,$ROOT/initramfs.img,"earlyprintk=serial console=ttyS0 modules=loop,squashfs,sd-mod,usb-storage vpnkit.connect=connect://2/1999" # hyperkit # -c 1 -m 1024M