Make DockerInstaller also install the Windows version of Docker

This commit is contained in:
Micah Lee 2020-02-11 16:48:52 -08:00
parent fa524d78b6
commit 17a585f614
No known key found for this signature in database
GPG key ID: 403C2657CD994F73
4 changed files with 161 additions and 57 deletions

View file

@ -31,8 +31,6 @@ Create a .deb:
## macOS
## macOS
Install Xcode from the Mac App Store. Once it's installed, run it for the first time to set it up. Also, run this to make sure command line tools are installed: `xcode-select --install`. And finally, open Xcode, go to Preferences > Locations, and make sure under Command Line Tools you select an installed version from the dropdown. (This is required for installing Qt5.)
Download and install Python 3.7.4 from https://www.python.org/downloads/release/python-374/. I downloaded `python-3.7.4-macosx10.9.pkg`.
@ -70,4 +68,22 @@ And then run `build_app.py --with-codesign`:
pipenv run ./install/macos/build_app.py --with-codesign
```
The output is in the `dist` folder.
The output is in the `dist` folder.
## Windows
Download Python 3.7.6, 32-bit (x86) from https://www.python.org/downloads/release/python-376/. I downloaded python-3.7.6.exe. When installing it, make sure to check the "Add Python 3.7 to PATH" checkbox on the first page of the installer.
Open a command prompt and cd to the gpgsync folder. If you don't have it already, install pipenv (`pip install pipenv`). Then install dependencies:
```
python -m pipenv install --dev
```
Install the Qt 5.14.1 from https://www.qt.io/offline-installers. I downloaded qt-opensource-windows-x86-5.14.1.exe. In the installer, unfortunately you have login to an account. Then all you need `Qt` > `Qt 5.14.1` > `MSVC 2017 32-bit`.
After that you can launch GPG Sync during development with:
```
python -m pipenv run python dev_scripts\dangerzone
```

View file

@ -8,7 +8,12 @@ import time
from .common import Common
from .main_window import MainWindow
from .docker_installer import is_docker_installed, is_docker_ready, DockerInstaller
from .docker_installer import (
is_docker_installed,
is_docker_ready,
launch_docker_windows,
DockerInstaller,
)
dangerzone_version = "0.1.0"
@ -49,6 +54,30 @@ def main(filename):
return
if platform.system() == "Windows":
if not is_docker_installed(common):
print("Docker is not installed")
docker_installer = DockerInstaller(common)
docker_installer.start()
# Quit after the installer runs, because it requires rebooting
return
if not is_docker_ready(common):
print("Docker is not running")
launch_docker_windows()
# Wait up to 20 minutes for docker to be ready
for i in range(120):
if 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")
# Main window
main_window = MainWindow(common)

View file

@ -30,8 +30,14 @@ class Common(object):
# Temporary directory to store pixel data
# Note in macOS, temp dirs must be in /tmp (or a few other paths) for Docker to mount them
self.pixel_dir = tempfile.TemporaryDirectory(prefix="/tmp/dangerzone-pixel-")
self.safe_dir = tempfile.TemporaryDirectory(prefix="/tmp/dangerzone-safe-")
if platform.system() == "Windows":
self.pixel_dir = tempfile.TemporaryDirectory(prefix="dangerzone-pixel-")
self.safe_dir = tempfile.TemporaryDirectory(prefix="dangerzone-safe-")
else:
self.pixel_dir = tempfile.TemporaryDirectory(
prefix="/tmp/dangerzone-pixel-"
)
self.safe_dir = tempfile.TemporaryDirectory(prefix="/tmp/dangerzone-safe-")
print(
f"Temporary directories created, dangerous={self.pixel_dir.name}, safe={self.safe_dir.name}"
)
@ -51,6 +57,10 @@ class Common(object):
# Container runtime
if platform.system() == "Darwin":
self.container_runtime = "/usr/local/bin/docker"
elif platform.system() == "Windows":
self.container_runtime = (
"C:\\Program Files\\Docker\\Docker\\resources\\bin\\docker.exe"
)
else:
self.container_runtime = "podman"

View file

@ -5,17 +5,23 @@ import tempfile
import subprocess
import shutil
import time
import platform
from PyQt5 import QtCore, QtGui, QtWidgets
def is_docker_installed(common):
# Does the docker binary exist?
if os.path.isdir("/Applications/Docker.app") and os.path.exists(
common.container_runtime
):
# Is it executable?
st = os.stat(common.container_runtime)
return bool(st.st_mode & stat.S_IXOTH)
if platform.system() == "Darwin":
# Does the docker binary exist?
if os.path.isdir("/Applications/Docker.app") and os.path.exists(
common.container_runtime
):
# Is it executable?
st = os.stat(common.container_runtime)
return bool(st.st_mode & stat.S_IXOTH)
if platform.system() == "Windows":
return os.path.exists(common.container_runtime)
return False
@ -28,6 +34,11 @@ def is_docker_ready(common):
return False
def launch_docker_windows():
docker_desktop_path = "C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"
subprocess.Popen([docker_desktop_path])
class DockerInstaller(QtWidgets.QDialog):
def __init__(self, common):
super(DockerInstaller, self).__init__()
@ -36,7 +47,11 @@ class DockerInstaller(QtWidgets.QDialog):
self.setWindowTitle("dangerzone")
self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path("logo.png")))
label = QtWidgets.QLabel("Dangerzone for macOS requires Docker")
label = QtWidgets.QLabel()
if platform.system() == "Darwin":
label.setText("Dangerzone for macOS requires Docker")
elif platform.system() == "Windows":
label.setText("Dangerzone for Windows requires Docker")
label.setStyleSheet("QLabel { font-weight: bold; }")
label.setAlignment(QtCore.Qt.AlignCenter)
@ -50,17 +65,21 @@ class DockerInstaller(QtWidgets.QDialog):
self.install_button.setStyleSheet("QPushButton { font-weight: bold; }")
self.install_button.clicked.connect(self.install_clicked)
self.install_button.hide()
self.launch_button = QtWidgets.QPushButton("Launch Docker")
self.launch_button.setStyleSheet("QPushButton { font-weight: bold; }")
self.launch_button.clicked.connect(self.launch_clicked)
self.launch_button.hide()
if platform.system() == "Darwin":
self.launch_button = QtWidgets.QPushButton("Launch Docker")
self.launch_button.setStyleSheet("QPushButton { font-weight: bold; }")
self.launch_button.clicked.connect(self.launch_clicked)
self.launch_button.hide()
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)
if platform.system() == "Darwin":
buttons_layout.addWidget(self.launch_button)
buttons_layout.addWidget(self.cancel_button)
buttons_layout.addStretch()
@ -72,8 +91,14 @@ class DockerInstaller(QtWidgets.QDialog):
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")
if platform.system == "Darwin":
self.tmp_dir = tempfile.TemporaryDirectory(prefix="/tmp/dangerzone-docker-")
self.installer_filename = os.path.join(self.tmp_dir.name, "Docker.dmg")
else:
self.tmp_dir = tempfile.TemporaryDirectory(prefix="dangerzone-docker-")
self.installer_filename = os.path.join(
self.tmp_dir.name, "Docker for Windows Installer.exe"
)
# Threads
self.download_t = None
@ -105,19 +130,22 @@ class DockerInstaller(QtWidgets.QDialog):
self.timer.start(10)
def start_download(self):
self.download_t = Downloader(self.dmg_filename)
self.download_t = Downloader(self.installer_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_finished(self):
self.task_label.setText("Finished installing Docker")
if platform.system() == "Darwin":
self.task_label.setText("Finished installing Docker")
self.launch_button.show()
self.cancel_button.setEnabled(True)
elif platform.system == "Windows":
self.task_label.setText("Reboot to finish installing Docker")
self.install_t = None
self.progress.hide()
self.install_button.hide()
self.launch_button.show()
self.cancel_button.setEnabled(True)
def install_failed(self, exception):
print(f"Install failed: {exception}")
@ -141,16 +169,17 @@ class DockerInstaller(QtWidgets.QDialog):
self.timer.start(10)
def start_installer(self):
self.install_t = Installer(self.dmg_filename)
self.install_t = Installer(self.installer_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"])
if system.platform() == "Darwin":
print("Launching Docker")
self.accept()
subprocess.Popen(["open", "-a", "Docker.app"])
def cancel_clicked(self):
self.reject()
@ -175,22 +204,25 @@ class Downloader(QtCore.QThread):
download_failed = QtCore.pyqtSignal(int)
update_progress = QtCore.pyqtSignal(int, int)
def __init__(self, dmg_filename):
def __init__(self, installer_filename):
super(Downloader, self).__init__()
self.dmg_filename = dmg_filename
self.installer_filename = installer_filename
if platform.system() == "Darwin":
self.installer_url = "https://download.docker.com/mac/stable/Docker.dmg"
elif platform.system() == "Windows":
self.installer_url = "https://download.docker.com/win/stable/Docker%20for%20Windows%20Installer.exe"
def run(self):
print(f"Downloading docker to {self.dmg_filename}")
with requests.get(
"https://download.docker.com/mac/stable/Docker.dmg", stream=True
) as r:
print(f"Downloading docker to {self.installer_filename}")
with requests.get(self.installer_url, stream=True) as r:
if r.status_code != 200:
self.download_failed.emit(r.status_code)
return
total_bytes = int(r.headers.get("content-length"))
downloaded_bytes = 0
with open(self.dmg_filename, "wb") as f:
with open(self.installer_filename, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
if chunk: # filter out keep-alive new chunks
downloaded_bytes += f.write(chunk)
@ -205,34 +237,51 @@ class Installer(QtCore.QThread):
install_failed = QtCore.pyqtSignal(str)
update_task_label = QtCore.pyqtSignal(str)
def __init__(self, dmg_filename):
def __init__(self, installer_filename):
super(Installer, self).__init__()
self.dmg_filename = dmg_filename
self.installer_filename = installer_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")
if platform.system() == "Darwin":
try:
# Mount the dmg
self.update_task_label.emit(f"Mounting Docker.dmg")
subprocess.run(
["hdiutil", "attach", "-nobrowse", self.installer_filename]
)
# Sync
self.update_task_label.emit("Syncing filesystem")
subprocess.run(["sync"])
# Copy Docker.app to Applications
self.update_task_label.emit("Copying Docker into Applications")
shutil.copytree(
"/Volumes/Docker/Docker.app", "/Applications/Docker.app"
)
# Wait, to prevent early crash
time.sleep(1)
# Sync
self.update_task_label.emit("Syncing filesystem")
subprocess.run(["sync"])
# Unmount the dmg
self.update_task_label.emit(f"Unmounting /Volumes/Docker")
subprocess.run(["hdiutil", "detach", "/Volumes/Docker"])
# Wait, to prevent early crash
time.sleep(1)
self.install_finished.emit()
# 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
elif platform.system() == "Windows":
try:
# Run the installer
subprocess.run([self.installer_filename])
self.install_finished.emit()
except Exception as e:
self.install_failed.emit(str(e))
return
except Exception as e:
self.install_failed.emit(str(e))
return