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 platform
import click
import time
from .common import Common
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"
@ -31,8 +32,19 @@ def main(filename):
if platform.system() == "Darwin" and not is_docker_installed(common):
print("Docker is not installed!")
docker_installer = DockerInstaller(common)
if docker_installer.launch():
main(filename)
if docker_installer.start():
# 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
# Main window

View file

@ -2,11 +2,14 @@ import os
import stat
import requests
import tempfile
import subprocess
import shutil
import time
from PyQt5 import QtCore, QtGui, QtWidgets
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(
common.container_runtime
):
@ -16,6 +19,15 @@ def is_docker_installed(common):
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):
def __init__(self, common):
super(DockerInstaller, self).__init__()
@ -26,8 +38,10 @@ class DockerInstaller(QtWidgets.QDialog):
label = QtWidgets.QLabel("Dangerzone for macOS requires Docker")
label.setStyleSheet("QLabel { font-weight: bold; }")
label.setAlignment(QtCore.Qt.AlignCenter)
self.task_label = QtWidgets.QLabel()
self.task_label.setAlignment(QtCore.Qt.AlignCenter)
self.progress = QtWidgets.QProgressBar()
self.progress.setMinimum(0)
@ -40,14 +54,14 @@ class DockerInstaller(QtWidgets.QDialog):
self.launch_button.setStyleSheet("QPushButton { font-weight: bold; }")
self.launch_button.clicked.connect(self.launch_clicked)
self.launch_button.hide()
cancel_button = QtWidgets.QPushButton("Cancel")
cancel_button.clicked.connect(self.cancel_clicked)
self.cancel_button = QtWidgets.QPushButton("Cancel")
self.cancel_button.clicked.connect(self.cancel_clicked)
buttons_layout = QtWidgets.QHBoxLayout()
buttons_layout.addStretch()
buttons_layout.addWidget(self.install_button)
buttons_layout.addWidget(self.launch_button)
buttons_layout.addWidget(cancel_button)
buttons_layout.addWidget(self.cancel_button)
buttons_layout.addStretch()
layout = QtWidgets.QVBoxLayout()
@ -55,29 +69,27 @@ class DockerInstaller(QtWidgets.QDialog):
layout.addWidget(self.task_label)
layout.addWidget(self.progress)
layout.addLayout(buttons_layout)
layout.addStretch()
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
self.download_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")
self.install_t = None
def update_progress(self, value, maximum):
self.progress.setMaximum(maximum)
self.progress.setValue(value)
def update_task_label(self, s):
self.task_label.setText(s)
def download_finished(self):
self.task_label.setText("Finished downloading Docker")
self.download_t = None
self.progress.hide()
self.install_button.show()
def download_failed(self, status_code):
@ -86,17 +98,72 @@ class DockerInstaller(QtWidgets.QDialog):
def download(self):
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_failed.connect(self.download_failed)
self.download_t.update_progress.connect(self.update_progress)
self.download_t.start()
def install(self):
pass
def install_finished(self):
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):
self.download()
def install_failed(self, exception):
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
@ -105,10 +172,9 @@ class Downloader(QtCore.QThread):
download_failed = QtCore.pyqtSignal(int)
update_progress = QtCore.pyqtSignal(int, int)
def __init__(self):
def __init__(self, dmg_filename):
super(Downloader, self).__init__()
self.tmp_dir = tempfile.TemporaryDirectory(prefix="/tmp/dangerzone-docker-")
self.dmg_filename = os.path.join(self.tmp_dir.name, "Docker.dmg")
self.dmg_filename = dmg_filename
def run(self):
print(f"Downloading docker to {self.dmg_filename}")
@ -130,3 +196,40 @@ class Downloader(QtCore.QThread):
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