mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-04-28 18:02:38 +02:00
Get WaitingWidget to properly check for and install the image
This commit is contained in:
parent
83759d1a33
commit
a54e19fe11
4 changed files with 29 additions and 494 deletions
|
@ -4,7 +4,7 @@ import inspect
|
|||
import appdirs
|
||||
import platform
|
||||
import subprocess
|
||||
import pipes
|
||||
import shutil
|
||||
import json
|
||||
import gzip
|
||||
import colorama
|
||||
|
@ -35,8 +35,12 @@ class GlobalCommon(object):
|
|||
# App data folder
|
||||
self.appdata_path = appdirs.user_config_dir("dangerzone")
|
||||
|
||||
# Container name
|
||||
# Container
|
||||
self.container_name = "dangerzone.rocks/dangerzone"
|
||||
if platform.system() == "Linux":
|
||||
self.container_runtime = shutil.which("podman")
|
||||
else:
|
||||
self.container_runtime = shutil.which("docker")
|
||||
|
||||
# Languages supported by tesseract
|
||||
self.ocr_languages = {
|
||||
|
@ -467,12 +471,10 @@ class GlobalCommon(object):
|
|||
# Load the container into podman
|
||||
print("Installing Dangerzone container...")
|
||||
|
||||
p = subprocess.Popen(["podman", "load"], stdin=subprocess.PIPE)
|
||||
p = subprocess.Popen([self.container_runtime, "load"], stdin=subprocess.PIPE)
|
||||
|
||||
chunk_size = 1024
|
||||
compressed_container_path = self.get_resource_path(
|
||||
"container/dangerzone.tar.gz"
|
||||
)
|
||||
compressed_container_path = self.get_resource_path("dangerzone-converter.tar.gz")
|
||||
with gzip.open(compressed_container_path) as f:
|
||||
while True:
|
||||
chunk = f.read(chunk_size)
|
||||
|
@ -493,18 +495,30 @@ class GlobalCommon(object):
|
|||
"""
|
||||
See if the podman container is installed. Linux only.
|
||||
"""
|
||||
print("Checking if container is already installed")
|
||||
|
||||
# Get the image id
|
||||
with open(self.get_resource_path("image-id.txt")) as f:
|
||||
image_id = f.read().strip()
|
||||
expected_image_id = f.read().strip()
|
||||
|
||||
# See if this image is already installed
|
||||
installed = False
|
||||
|
||||
if platform.system() == "Linux":
|
||||
# Podman
|
||||
images = json.loads(
|
||||
subprocess.check_output(["podman", "image", "list", "--format", "json"])
|
||||
subprocess.check_output([self.container_runtime, "image", "list", "--format", "json"])
|
||||
)
|
||||
for image in images:
|
||||
if image["Id"] == image_id:
|
||||
if image["Id"] == expected_image_id:
|
||||
installed = True
|
||||
break
|
||||
else:
|
||||
# Docker
|
||||
found_image_id = subprocess.check_output([self.container_runtime, "image", "list", "--format", "{{.ID}}", self.container_name], text=True)
|
||||
if found_image_id.strip() == expected_image_id:
|
||||
installed = True
|
||||
else:
|
||||
print(f"Image {found_image_id} is installed, not {expected_image_id}")
|
||||
|
||||
return installed
|
||||
|
|
|
@ -95,9 +95,7 @@ class InstallContainerThread(QtCore.QThread):
|
|||
self.global_common = global_common
|
||||
|
||||
def run(self):
|
||||
if not self.global_common.is_container_installed():
|
||||
self.global_common.install_container()
|
||||
|
||||
self.finished.emit()
|
||||
|
||||
|
||||
|
@ -170,6 +168,7 @@ class WaitingWidget(QtWidgets.QWidget):
|
|||
state = "install_container"
|
||||
|
||||
# Update the state
|
||||
print(f"Dangerzone state: {state}")
|
||||
self.state_change(state)
|
||||
|
||||
def state_change(self, state):
|
||||
|
|
|
@ -1,467 +0,0 @@
|
|||
import os
|
||||
import subprocess
|
||||
import uuid
|
||||
import pipes
|
||||
import tempfile
|
||||
import socket
|
||||
import random
|
||||
import getpass
|
||||
import json
|
||||
import psutil
|
||||
import time
|
||||
import platform
|
||||
import shutil
|
||||
from PySide2 import QtCore
|
||||
|
||||
|
||||
class Vm(QtCore.QObject):
|
||||
STATE_OFF = 0
|
||||
STATE_STARTING = 1
|
||||
STATE_ON = 2
|
||||
STATE_FAIL = 3
|
||||
|
||||
vm_state_change = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, global_common, allow_vm_login):
|
||||
super(Vm, self).__init__()
|
||||
self.global_common = global_common
|
||||
self.allow_vm_login = allow_vm_login
|
||||
|
||||
# VM starts off
|
||||
self.state = self.STATE_OFF
|
||||
|
||||
# Ports for ssh services
|
||||
self.sshd_port = None
|
||||
self.sshd_tunnel_port = None
|
||||
|
||||
# Folder to hold temporary files related to the VM
|
||||
self.state_dir = tempfile.TemporaryDirectory()
|
||||
self.ssh_host_key_path = os.path.join(self.state_dir.name, "host_ed25519")
|
||||
self.ssh_host_pubkey_path = os.path.join(
|
||||
self.state_dir.name, "host_ed25519.pub"
|
||||
)
|
||||
self.ssh_client_key_path = os.path.join(self.state_dir.name, "client_ed25519")
|
||||
self.ssh_client_pubkey_path = os.path.join(
|
||||
self.state_dir.name, "client_ed25519.pub"
|
||||
)
|
||||
self.sshd_pid_path = os.path.join(self.state_dir.name, "sshd.pid")
|
||||
self.sshd_log_path = os.path.join(self.state_dir.name, "sshd.log")
|
||||
self.vm_info_path = os.path.join(self.state_dir.name, "info.json")
|
||||
self.vm_disk_img_path = os.path.join(self.state_dir.name, "disk.img")
|
||||
|
||||
self.vm_iso_path = self.global_common.get_resource_path("vm/dangerzone.iso")
|
||||
|
||||
if platform.system() == "Darwin":
|
||||
self.ssh_keygen_path = shutil.which("ssh-keygen")
|
||||
self.sshd_path = shutil.which("sshd")
|
||||
|
||||
# Processes
|
||||
self.vpnkit_p = None
|
||||
self.hyperkit_p = None
|
||||
self.devnull = open(os.devnull, "w")
|
||||
|
||||
# Relevant paths
|
||||
self.vpnkit_path = self.global_common.get_resource_path("bin/vpnkit")
|
||||
self.hyperkit_path = self.global_common.get_resource_path("bin/hyperkit")
|
||||
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"
|
||||
)
|
||||
|
||||
# Temporary files related to the VM
|
||||
self.vpnkit_sock_path = os.path.join(self.state_dir.name, "vpnkit.eth.sock")
|
||||
self.hyperkit_pid_path = os.path.join(self.state_dir.name, "hyperkit.pid")
|
||||
|
||||
# UDID for VM
|
||||
self.vm_uuid = str(uuid.uuid4())
|
||||
self.vm_cmdline = (
|
||||
"earlyprintk=serial console=ttyS0 modules=loop,squashfs,sd-mod"
|
||||
)
|
||||
|
||||
if platform.system() == "Windows":
|
||||
self.vboxmanage_path = (
|
||||
"C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe"
|
||||
)
|
||||
|
||||
self.ssh_keygen_path = os.path.join(
|
||||
self.global_common.get_resource_path("bin"), "ssh-keygen.exe"
|
||||
)
|
||||
self.sshd_path = os.path.join(
|
||||
self.global_common.get_resource_path("bin"), "sshd.exe"
|
||||
)
|
||||
|
||||
# Threads
|
||||
self.wait_t = None
|
||||
|
||||
def __del__(self):
|
||||
self.stop()
|
||||
|
||||
def start(self):
|
||||
print("Starting VM\n")
|
||||
|
||||
# Delete keys if they already exist
|
||||
for filename in [
|
||||
self.ssh_host_key_path,
|
||||
self.ssh_host_pubkey_path,
|
||||
self.ssh_client_key_path,
|
||||
self.ssh_client_pubkey_path,
|
||||
]:
|
||||
if os.path.exists(filename):
|
||||
os.remove(filename)
|
||||
|
||||
# Find an open port
|
||||
self.sshd_port = self.find_open_port()
|
||||
self.sshd_tunnel_port = self.find_open_port()
|
||||
|
||||
# Generate new keys
|
||||
subprocess.run(
|
||||
[
|
||||
self.ssh_keygen_path,
|
||||
"-t",
|
||||
"ed25519",
|
||||
"-C",
|
||||
"dangerzone-host",
|
||||
"-N",
|
||||
"",
|
||||
"-f",
|
||||
self.ssh_host_key_path,
|
||||
],
|
||||
stdout=self.devnull,
|
||||
stderr=self.devnull,
|
||||
)
|
||||
subprocess.run(
|
||||
[
|
||||
self.ssh_keygen_path,
|
||||
"-t",
|
||||
"ed25519",
|
||||
"-C",
|
||||
"dangerzone-client",
|
||||
"-N",
|
||||
"",
|
||||
"-f",
|
||||
self.ssh_client_key_path,
|
||||
],
|
||||
stdout=self.devnull,
|
||||
stderr=self.devnull,
|
||||
)
|
||||
with open(self.ssh_client_key_path) as f:
|
||||
self.ssh_client_key = f.read()
|
||||
with open(self.ssh_client_pubkey_path) as f:
|
||||
self.ssh_client_pubkey = f.read()
|
||||
|
||||
# Start an sshd service on this port
|
||||
args = [
|
||||
self.sshd_path,
|
||||
"-4",
|
||||
"-E",
|
||||
self.sshd_log_path,
|
||||
"-o",
|
||||
f"PidFile={self.sshd_pid_path}",
|
||||
"-o",
|
||||
f"HostKey={self.ssh_host_key_path}",
|
||||
"-o",
|
||||
f"ListenAddress=127.0.0.1:{self.sshd_port}",
|
||||
"-o",
|
||||
f"AllowUsers={getpass.getuser()}",
|
||||
"-o",
|
||||
"PasswordAuthentication=no",
|
||||
"-o",
|
||||
"PubkeyAuthentication=yes",
|
||||
"-o",
|
||||
"Compression=yes",
|
||||
"-o",
|
||||
"UseDNS=no",
|
||||
"-o",
|
||||
f"AuthorizedKeysFile={self.ssh_client_pubkey_path}",
|
||||
"-o",
|
||||
"ForceCommand=/sbin/nologin",
|
||||
]
|
||||
args_str = " ".join(pipes.quote(s) for s in args)
|
||||
print("> " + args_str)
|
||||
subprocess.run(args, stdout=self.devnull, stderr=self.devnull)
|
||||
|
||||
if platform.system() == "Darwin":
|
||||
self.start_macos()
|
||||
|
||||
if platform.system() == "Windows":
|
||||
self.start_windows()
|
||||
|
||||
def start_macos(self):
|
||||
self.state = self.STATE_STARTING
|
||||
self.vm_state_change.emit(self.state)
|
||||
|
||||
# Create a JSON object to pass into the VM
|
||||
# This is a 512kb file that starts with a JSON object, followed by null bytes
|
||||
guest_vm_info = {
|
||||
"id_ed25519": self.ssh_client_key,
|
||||
"id_ed25519.pub": self.ssh_client_pubkey,
|
||||
"user": getpass.getuser(),
|
||||
"ip": "192.168.65.2",
|
||||
"port": self.sshd_port,
|
||||
"tunnel_port": self.sshd_tunnel_port,
|
||||
}
|
||||
with open(self.vm_disk_img_path, "wb") as f:
|
||||
guest_vm_info_bytes = json.dumps(guest_vm_info).encode()
|
||||
f.write(guest_vm_info_bytes)
|
||||
f.write(b"\x00" * (512 * 1024 - len(guest_vm_info_bytes)))
|
||||
|
||||
# Create a JSON object for the container process to read
|
||||
vm_info = {
|
||||
"client_key_path": self.ssh_client_key_path,
|
||||
"tunnel_port": self.sshd_tunnel_port,
|
||||
}
|
||||
with open(self.vm_info_path, "w") as f:
|
||||
f.write(json.dumps(vm_info))
|
||||
|
||||
# Run VPNKit
|
||||
args = [
|
||||
self.vpnkit_path,
|
||||
"--ethernet",
|
||||
self.vpnkit_sock_path,
|
||||
"--gateway-ip",
|
||||
"192.168.65.1",
|
||||
"--host-ip",
|
||||
"192.168.65.2",
|
||||
"--lowest-ip",
|
||||
"192.168.65.3",
|
||||
"--highest-ip",
|
||||
"192.168.65.254",
|
||||
]
|
||||
args_str = " ".join(pipes.quote(s) for s in args)
|
||||
print("> " + args_str)
|
||||
self.vpnkit_p = subprocess.Popen(args, stdout=self.devnull, stderr=self.devnull)
|
||||
|
||||
# Run Hyperkit
|
||||
args = [
|
||||
self.hyperkit_path,
|
||||
"-F",
|
||||
self.hyperkit_pid_path,
|
||||
"-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",
|
||||
f"2:0,virtio-vpnkit,path={self.vpnkit_sock_path}",
|
||||
"-s",
|
||||
f"3:0,virtio-blk,{self.vm_disk_img_path}",
|
||||
"-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)
|
||||
|
||||
if self.allow_vm_login:
|
||||
# Start the VM with the ability to login
|
||||
self.hyperkit_p = subprocess.Popen(args)
|
||||
else:
|
||||
# Start the VM without ability to login
|
||||
self.hyperkit_p = subprocess.Popen(
|
||||
args, stdout=self.devnull, stderr=self.devnull, stdin=self.devnull
|
||||
)
|
||||
|
||||
# Wait for SSH thread
|
||||
self.wait_t = WaitForSsh(self.sshd_tunnel_port)
|
||||
self.wait_t.connected.connect(self.vm_connected)
|
||||
self.wait_t.timeout.connect(self.vm_timeout)
|
||||
self.wait_t.start()
|
||||
|
||||
def start_windows(self):
|
||||
vm_name = "dangezone-podman"
|
||||
basefolder_path = os.path.join(
|
||||
self.global_common.appdata_path, "virtualbox-basefolder"
|
||||
)
|
||||
|
||||
# See if we already have a VM
|
||||
exists = False
|
||||
for line in subprocess.check_output([self.vboxmanage_path, "list", "vms"]):
|
||||
name = line.split()[0].lstrip('"').rstrip('"')
|
||||
if name == vm_name:
|
||||
exists = True
|
||||
break
|
||||
|
||||
# Create the VM
|
||||
if not exists:
|
||||
subprocess.run(
|
||||
[
|
||||
self.vboxmanage_path,
|
||||
"createvm",
|
||||
"--name",
|
||||
vm_name,
|
||||
"--basefolder",
|
||||
basefolder_path,
|
||||
"--ostype",
|
||||
"Linux_x64",
|
||||
"--register",
|
||||
]
|
||||
)
|
||||
|
||||
# Configure the VM
|
||||
subprocess.run(
|
||||
[
|
||||
self.vboxmanage_path,
|
||||
"modifyvm",
|
||||
vm_name,
|
||||
"--memory",
|
||||
"4096",
|
||||
"--nic1",
|
||||
"nat",
|
||||
"--cableconnected1",
|
||||
"on",
|
||||
]
|
||||
)
|
||||
subprocess.run(
|
||||
[
|
||||
self.vboxmanage_path,
|
||||
"storagectl",
|
||||
vm_name,
|
||||
"--name",
|
||||
"DangerzoneBoot",
|
||||
"--add",
|
||||
"ide",
|
||||
"--bootable",
|
||||
"on",
|
||||
]
|
||||
)
|
||||
subprocess.run(
|
||||
[
|
||||
self.vboxmanage_path,
|
||||
"storageattach",
|
||||
vm_name,
|
||||
"--storagectl",
|
||||
"DangerzoneBoot",
|
||||
"--port",
|
||||
"1",
|
||||
"--device",
|
||||
"1",
|
||||
"--type",
|
||||
"dvddrive",
|
||||
"--medium",
|
||||
self.vm_iso_path,
|
||||
]
|
||||
)
|
||||
|
||||
# Start the VM
|
||||
subprocess.run([self.vboxmanage_path, "startvm", "--type", "headless"])
|
||||
|
||||
def vm_connected(self):
|
||||
self.state = self.STATE_ON
|
||||
self.vm_state_change.emit(self.state)
|
||||
|
||||
def vm_timeout(self):
|
||||
self.state = self.STATE_FAIL
|
||||
self.vm_state_change.emit(self.state)
|
||||
|
||||
def stop(self):
|
||||
self.kill_sshd()
|
||||
|
||||
if platform.system() == "Darwin":
|
||||
self.stop_macos()
|
||||
|
||||
if platform.system() == "Windows":
|
||||
self.stop_windows()
|
||||
|
||||
def stop_macos(self):
|
||||
# Kill existing processes
|
||||
if self.vpnkit_p is not None:
|
||||
self.vpnkit_p.terminate()
|
||||
self.vpnkit_p = None
|
||||
if self.hyperkit_p is not None:
|
||||
self.hyperkit_p.terminate()
|
||||
self.hyperkit_p = None
|
||||
|
||||
# Just to be extra sure
|
||||
self.kill_hyperkit()
|
||||
|
||||
def stop_windows(self):
|
||||
vm_name = "dangezone-podman"
|
||||
subprocess.run(
|
||||
[
|
||||
self.vboxmanage_path,
|
||||
"controlvm",
|
||||
vm_name,
|
||||
"poweroff",
|
||||
]
|
||||
)
|
||||
|
||||
def find_open_port(self):
|
||||
with socket.socket() as tmpsock:
|
||||
while True:
|
||||
try:
|
||||
tmpsock.bind(("127.0.0.1", random.randint(1024, 65535)))
|
||||
break
|
||||
except OSError:
|
||||
pass
|
||||
_, port = tmpsock.getsockname()
|
||||
|
||||
return port
|
||||
|
||||
def kill_sshd(self):
|
||||
if os.path.exists(self.sshd_pid_path):
|
||||
with open(self.sshd_pid_path) as f:
|
||||
sshd_pid = int(f.read())
|
||||
|
||||
if psutil.pid_exists(sshd_pid):
|
||||
try:
|
||||
proc = psutil.Process(sshd_pid)
|
||||
proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def kill_hyperkit(self):
|
||||
if os.path.exists(self.hyperkit_pid_path):
|
||||
with open(self.hyperkit_pid_path) as f:
|
||||
hyperkit_pid = int(f.read())
|
||||
|
||||
if psutil.pid_exists(hyperkit_pid):
|
||||
try:
|
||||
proc = psutil.Process(hyperkit_pid)
|
||||
proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class WaitForSsh(QtCore.QThread):
|
||||
connected = QtCore.Signal()
|
||||
timeout = QtCore.Signal()
|
||||
|
||||
def __init__(self, ssh_port):
|
||||
super(WaitForSsh, self).__init__()
|
||||
self.ssh_port = ssh_port
|
||||
|
||||
def run(self):
|
||||
# Wait for the SSH port to be open
|
||||
success = False
|
||||
timeout_seconds = 45
|
||||
start_ts = time.time()
|
||||
while True:
|
||||
sock = socket.socket()
|
||||
try:
|
||||
sock.connect(("127.0.0.1", int(self.ssh_port)))
|
||||
sock.close()
|
||||
success = True
|
||||
print("\nVM is ready to use")
|
||||
break
|
||||
except Exception:
|
||||
so_far = int(time.time() - start_ts)
|
||||
print(f"\rWaiting for SSH port to be available ({so_far}s)", end="")
|
||||
pass
|
||||
|
||||
time.sleep(1)
|
||||
if time.time() - start_ts >= timeout_seconds:
|
||||
self.timeout.emit()
|
||||
break
|
||||
|
||||
if success:
|
||||
self.connected.emit()
|
|
@ -1,11 +0,0 @@
|
|||
REM Build ISO
|
||||
cd install\vm-builder
|
||||
vagrant up
|
||||
vagrant ssh -- dos2unix /vagrant/windows.sh
|
||||
vagrant ssh -- /vagrant/windows.sh
|
||||
vagrant halt
|
||||
cd ..\..
|
||||
|
||||
REM Copy the ISO to resources
|
||||
mkdir share\vm
|
||||
copy install\vm-builder\vm\dangerzone.iso share\vm
|
Loading…
Reference in a new issue