mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-04-28 18:02:38 +02:00
After downloading Docker, install it, launch it for the first time, and once it's ready, re-launch dangerzone
This commit is contained in:
parent
abb56b68ac
commit
13aac3348a
2 changed files with 141 additions and 26 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue