After downloading Docker, install it, launch it for the first time, and once it's ready, re-launch dangerzone

This commit is contained in:
Micah Lee 2020-02-07 11:29:43 -08:00
parent abb56b68ac
commit 13aac3348a
No known key found for this signature in database
GPG key ID: 403C2657CD994F73
2 changed files with 141 additions and 26 deletions

View file

@ -4,10 +4,11 @@ import sys
import signal import signal
import platform import platform
import click import click
import time
from .common import Common from .common import Common
from .main_window import MainWindow from .main_window import MainWindow
from .docker_installer import is_docker_installed, DockerInstaller from .docker_installer import is_docker_installed, is_docker_ready, DockerInstaller
dangerzone_version = "0.1.0" dangerzone_version = "0.1.0"
@ -31,8 +32,19 @@ def main(filename):
if platform.system() == "Darwin" and not is_docker_installed(common): if platform.system() == "Darwin" and not is_docker_installed(common):
print("Docker is not installed!") print("Docker is not installed!")
docker_installer = DockerInstaller(common) docker_installer = DockerInstaller(common)
if docker_installer.launch(): if docker_installer.start():
main(filename) # When installer finished, wait up to 20 minutes for the user to launch it
for i in range(120):
if is_docker_installed(common) and is_docker_ready(common):
main(filename)
return
print("Waiting for docker to be available ...")
time.sleep(1)
# Give up
print("Docker not available, giving up")
return return
# Main window # Main window

View file

@ -2,11 +2,14 @@ import os
import stat import stat
import requests import requests
import tempfile import tempfile
import subprocess
import shutil
import time
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
def is_docker_installed(common): def is_docker_installed(common):
# Soes the docker binary exist? # Does the docker binary exist?
if os.path.isdir("/Applications/Docker.app") and os.path.exists( if os.path.isdir("/Applications/Docker.app") and os.path.exists(
common.container_runtime common.container_runtime
): ):
@ -16,6 +19,15 @@ def is_docker_installed(common):
return False return False
def is_docker_ready(common):
# Run `docker ps` without an error
try:
subprocess.run([common.container_runtime, "ps"], check=True)
return True
except subprocess.CalledProcessError:
return False
class DockerInstaller(QtWidgets.QDialog): class DockerInstaller(QtWidgets.QDialog):
def __init__(self, common): def __init__(self, common):
super(DockerInstaller, self).__init__() super(DockerInstaller, self).__init__()
@ -26,8 +38,10 @@ class DockerInstaller(QtWidgets.QDialog):
label = QtWidgets.QLabel("Dangerzone for macOS requires Docker") label = QtWidgets.QLabel("Dangerzone for macOS requires Docker")
label.setStyleSheet("QLabel { font-weight: bold; }") label.setStyleSheet("QLabel { font-weight: bold; }")
label.setAlignment(QtCore.Qt.AlignCenter)
self.task_label = QtWidgets.QLabel() self.task_label = QtWidgets.QLabel()
self.task_label.setAlignment(QtCore.Qt.AlignCenter)
self.progress = QtWidgets.QProgressBar() self.progress = QtWidgets.QProgressBar()
self.progress.setMinimum(0) self.progress.setMinimum(0)
@ -40,14 +54,14 @@ class DockerInstaller(QtWidgets.QDialog):
self.launch_button.setStyleSheet("QPushButton { font-weight: bold; }") self.launch_button.setStyleSheet("QPushButton { font-weight: bold; }")
self.launch_button.clicked.connect(self.launch_clicked) self.launch_button.clicked.connect(self.launch_clicked)
self.launch_button.hide() self.launch_button.hide()
cancel_button = QtWidgets.QPushButton("Cancel") self.cancel_button = QtWidgets.QPushButton("Cancel")
cancel_button.clicked.connect(self.cancel_clicked) self.cancel_button.clicked.connect(self.cancel_clicked)
buttons_layout = QtWidgets.QHBoxLayout() buttons_layout = QtWidgets.QHBoxLayout()
buttons_layout.addStretch() buttons_layout.addStretch()
buttons_layout.addWidget(self.install_button) buttons_layout.addWidget(self.install_button)
buttons_layout.addWidget(self.launch_button) buttons_layout.addWidget(self.launch_button)
buttons_layout.addWidget(cancel_button) buttons_layout.addWidget(self.cancel_button)
buttons_layout.addStretch() buttons_layout.addStretch()
layout = QtWidgets.QVBoxLayout() layout = QtWidgets.QVBoxLayout()
@ -55,29 +69,27 @@ class DockerInstaller(QtWidgets.QDialog):
layout.addWidget(self.task_label) layout.addWidget(self.task_label)
layout.addWidget(self.progress) layout.addWidget(self.progress)
layout.addLayout(buttons_layout) layout.addLayout(buttons_layout)
layout.addStretch()
self.setLayout(layout) self.setLayout(layout)
self.tmp_dir = tempfile.TemporaryDirectory(prefix="/tmp/dangerzone-docker-")
self.dmg_filename = os.path.join(self.tmp_dir.name, "Docker.dmg")
# Threads # Threads
self.download_t = None self.download_t = None
self.install_t = None
def cancel_clicked(self):
if self.download_t:
self.download_t.terminate()
self.reject()
def install_clicked(self):
print("Install clicked")
def launch_clicked(self):
print("Launch clicked")
def update_progress(self, value, maximum): def update_progress(self, value, maximum):
self.progress.setMaximum(maximum) self.progress.setMaximum(maximum)
self.progress.setValue(value) self.progress.setValue(value)
def update_task_label(self, s):
self.task_label.setText(s)
def download_finished(self): def download_finished(self):
self.task_label.setText("Finished downloading Docker") self.task_label.setText("Finished downloading Docker")
self.download_t = None self.download_t = None
self.progress.hide()
self.install_button.show() self.install_button.show()
def download_failed(self, status_code): def download_failed(self, status_code):
@ -86,17 +98,72 @@ class DockerInstaller(QtWidgets.QDialog):
def download(self): def download(self):
self.task_label.setText("Downloading Docker") self.task_label.setText("Downloading Docker")
self.download_t = Downloader()
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.start_download)
self.timer.setSingleShot(True)
self.timer.start(10)
def start_download(self):
self.download_t = Downloader(self.dmg_filename)
self.download_t.download_finished.connect(self.download_finished) self.download_t.download_finished.connect(self.download_finished)
self.download_t.download_failed.connect(self.download_failed) self.download_t.download_failed.connect(self.download_failed)
self.download_t.update_progress.connect(self.update_progress) self.download_t.update_progress.connect(self.update_progress)
self.download_t.start() self.download_t.start()
def install(self): def install_finished(self):
pass self.task_label.setText("Finished installing Docker")
self.install_t = None
self.progress.hide()
self.install_button.hide()
self.launch_button.show()
self.cancel_button.setEnabled(True)
def launch(self): def install_failed(self, exception):
self.download() print(f"Install failed: {exception}")
self.task_label.setText(f"Install failed: {exception}")
self.install_t = None
self.cancel_button.setEnabled(True)
def install_clicked(self):
self.task_label.setText("Installing Docker")
self.install_button.hide()
self.cancel_button.setEnabled(False)
self.progress.setMinimum(0)
self.progress.setMaximum(0)
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.start_installer)
self.timer.setSingleShot(True)
self.timer.start(10)
def start_installer(self):
self.install_t = Installer(self.dmg_filename)
self.install_t.install_finished.connect(self.install_finished)
self.install_t.install_failed.connect(self.install_failed)
self.install_t.update_task_label.connect(self.update_task_label)
self.install_t.start()
def launch_clicked(self):
print("Launching Docker")
self.accept()
subprocess.Popen(["open", "-a", "Docker.app"])
def cancel_clicked(self):
if self.download_t:
self.download_t.terminate()
if self.install_t:
self.install_t.terminate()
self.reject()
def start(self):
if not os.path.isdir("/Applications/Docker.app"):
self.download()
else:
self.task_label.setText("Docker is installed, but you must launch it first")
self.progress.hide()
self.launch_button.show()
return self.exec_() == QtWidgets.QDialog.Accepted return self.exec_() == QtWidgets.QDialog.Accepted
@ -105,10 +172,9 @@ class Downloader(QtCore.QThread):
download_failed = QtCore.pyqtSignal(int) download_failed = QtCore.pyqtSignal(int)
update_progress = QtCore.pyqtSignal(int, int) update_progress = QtCore.pyqtSignal(int, int)
def __init__(self): def __init__(self, dmg_filename):
super(Downloader, self).__init__() super(Downloader, self).__init__()
self.tmp_dir = tempfile.TemporaryDirectory(prefix="/tmp/dangerzone-docker-") self.dmg_filename = dmg_filename
self.dmg_filename = os.path.join(self.tmp_dir.name, "Docker.dmg")
def run(self): def run(self):
print(f"Downloading docker to {self.dmg_filename}") print(f"Downloading docker to {self.dmg_filename}")
@ -130,3 +196,40 @@ class Downloader(QtCore.QThread):
self.download_finished.emit() self.download_finished.emit()
class Installer(QtCore.QThread):
install_finished = QtCore.pyqtSignal()
install_failed = QtCore.pyqtSignal(str)
update_task_label = QtCore.pyqtSignal(str)
def __init__(self, dmg_filename):
super(Installer, self).__init__()
self.dmg_filename = dmg_filename
def run(self):
print(f"Installing Docker")
try:
# Mount the dmg
self.update_task_label.emit(f"Mounting Docker.dmg")
subprocess.run(["hdiutil", "attach", "-nobrowse", self.dmg_filename])
# Copy Docker.app to Applications
self.update_task_label.emit("Copying Docker into Applications")
shutil.copytree("/Volumes/Docker/Docker.app", "/Applications/Docker.app")
# Sync
self.update_task_label.emit("Syncing filesystem")
subprocess.run(["sync"])
# Wait, to prevent early crash
time.sleep(1)
# Unmount the dmg
self.update_task_label.emit(f"Unmounting /Volumes/Docker")
subprocess.run(["hdiutil", "detach", "/Volumes/Docker"])
self.install_finished.emit()
except Exception as e:
self.install_failed.emit(str(e))
return