Compare commits

...

5 commits

Author SHA1 Message Date
787bb87d4e
Merge 48ad749965 into c407e2ff84 2025-01-20 14:10:53 +01:00
Alexis Métaireau
c407e2ff84
doc: update Debian Trixie installation instructions
Some checks are pending
Tests / windows (push) Blocked by required conditions
Tests / macOS (arch64) (push) Blocked by required conditions
Tests / macOS (x86_64) (push) Blocked by required conditions
Tests / build-deb (debian bookworm) (push) Blocked by required conditions
Tests / build-deb (debian bullseye) (push) Blocked by required conditions
Tests / build-deb (debian trixie) (push) Blocked by required conditions
Tests / build-deb (ubuntu 20.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 22.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 24.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 24.10) (push) Blocked by required conditions
Tests / install-deb (debian bookworm) (push) Blocked by required conditions
Tests / install-deb (debian bullseye) (push) Blocked by required conditions
Tests / install-deb (debian trixie) (push) Blocked by required conditions
Tests / install-deb (ubuntu 20.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 22.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 24.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 24.10) (push) Blocked by required conditions
Tests / build-install-rpm (fedora 40) (push) Blocked by required conditions
Tests / build-install-rpm (fedora 41) (push) Blocked by required conditions
Tests / run tests (debian bookworm) (push) Blocked by required conditions
Tests / run tests (debian bullseye) (push) Blocked by required conditions
Tests / run tests (debian trixie) (push) Blocked by required conditions
Tests / run tests (fedora 40) (push) Blocked by required conditions
Tests / run tests (fedora 41) (push) Blocked by required conditions
Tests / run tests (ubuntu 20.04) (push) Blocked by required conditions
Tests / run tests (ubuntu 22.04) (push) Blocked by required conditions
Tests / run tests (ubuntu 24.04) (push) Blocked by required conditions
Tests / run tests (ubuntu 24.10) (push) Blocked by required conditions
Scan latest app and container / security-scan-container (push) Waiting to run
Scan latest app and container / security-scan-app (push) Waiting to run
Starting with Debian Trixie, `apt secure` relies on `sqv` to do its verification, which doesn't support the GPG keybox database format.

At the same time, using the standard PGP base64 format makes the verification fail for versions of `apt secure` which relies on `gpg`, as the subkey isn't detected there.

Fixes #1055
2025-01-20 14:10:15 +01:00
Alexis Métaireau
48ad749965
doc: bump the Docker Desktop version as part of the RELEASE procedure 2025-01-16 11:51:00 +01:00
Alexis Métaireau
a7e39a04ad
Bind Alert instances to the main window alert property 2025-01-16 11:50:59 +01:00
Alexis Métaireau
abc72ffe0e
Warn users if the minimum version of Docker Desktop is not met
This only happens on Windows and macOS.

Fixes #693
2025-01-16 11:50:58 +01:00
7 changed files with 261 additions and 19 deletions

View file

@ -46,21 +46,30 @@ jobs:
apt update
apt-get install python-all -y
- name: Add GPG key for the packages.freedom.press
- name: Add packages.freedom.press PGP key (gpg)
if: matrix.version != 'trixie'
run: |
apt-get update && apt-get install -y gnupg2 ca-certificates
dirmngr # NOTE: This is a command that's necessary only in containers
# The key needs to be in the GPG keybox database format so the
# signing subkey is detected by apt-secure.
gpg --keyserver hkps://keys.openpgp.org \
--no-default-keyring --keyring ./fpf-apt-tools-archive-keyring.gpg \
--recv-keys "DE28 AB24 1FA4 8260 FAC9 B8BA A7C9 B385 2260 4281"
# Export the GPG key in armor mode because sequoia needs it this way
# (sqv is used on debian trixie by default to check the keys)
mkdir -p /etc/apt/keyrings/
gpg --no-default-keyring --keyring ./fpf-apt-tools-archive-keyring.gpg \
--armor --export "DE28 AB24 1FA4 8260 FAC9 B8BA A7C9 B385 2260 4281" \
> /etc/apt/keyrings/fpf-apt-tools-archive-keyring.gpg
mv ./fpf-apt-tools-archive-keyring.gpg /etc/apt/keyrings/.
- name: Add packages.freedom.press PGP key (sq)
if: matrix.version == 'trixie'
run: |
apt-get update && apt-get install -y ca-certificates sq
mkdir -p /etc/apt/keyrings/
# On debian trixie, apt-secure uses `sqv` to verify the signatures
# so we need to retrieve PGP keys and store them using the base64 format.
sq network keyserver \
--server hkps://keys.openpgp.org \
search "DE28 AB24 1FA4 8260 FAC9 B8BA A7C9 B385 2260 4281" \
--output /etc/apt/keyrings/fpf-apt-tools-archive-keyring.gpg
- name: Add packages.freedom.press to our APT sources
run: |
. /etc/os-release

View file

@ -84,9 +84,20 @@ Dangerzone is available for:
</tr>
</table>
Add our repository following these instructions:
First, retrieve the PGP keys.
Download the GPG key for the repo:
Starting with Trixie, follow these instructions to download the PGP keys:
```bash
sudo apt-get update && sudo apt-get install sq -y
mkdir -p /etc/apt/keyrings/
sq network keyserver \
--server hkps://keys.openpgp.org \
search "DE28 AB24 1FA4 8260 FAC9 B8BA A7C9 B385 2260 4281" \
--output /etc/apt/keyrings/fpf-apt-tools-archive-keyring.gpg
```
On other Debian-derivatives:
```sh
sudo apt-get update && sudo apt-get install gnupg2 ca-certificates -y
@ -99,7 +110,7 @@ sudo gpg --no-default-keyring --keyring ./fpf-apt-tools-archive-keyring.gpg \
> /etc/apt/keyrings/fpf-apt-tools-archive-keyring.gpg
```
Add the URL of the repo in your APT sources:
Then, on all distributions, add the URL of the repo in your APT sources:
```sh
. /etc/os-release

View file

@ -8,12 +8,13 @@ Here is a list of tasks that should be done before issuing the release:
- [ ] Create a new issue named **QA and Release for version \<VERSION\>**, to track the general progress.
You can generate its content with the the `poetry run ./dev_scripts/generate-release-tasks.py` command.
- [ ] [Add new Linux platforms and remove obsolete ones](https://github.com/freedomofpress/dangerzone/blob/main/RELEASE.md#add-new-platforms-and-remove-obsolete-ones)
- [ ] [Add new Linux platforms and remove obsolete ones](https://github.com/freedomofpress/dangerzone/blob/main/RELEASE.md#add-new-linux-platforms-and-remove-obsolete-ones)
- [ ] Bump the Python dependencies using `poetry lock`
- [ ] Update `version` in `pyproject.toml`
- [ ] Update `share/version.txt`
- [ ] Update the "Version" field in `install/linux/dangerzone.spec`
- [ ] Bump the Debian version by adding a new changelog entry in `debian/changelog`
- [ ] [Bump the minimum Docker Desktop versions](https://github.com/freedomofpress/dangerzone/blob/main/RELEASE.md#bump-the-minimum-docker-desktop-version) in `isolation_provider/container.py`
- [ ] Update screenshot in `README.md`, if necessary
- [ ] CHANGELOG.md should be updated to include a list of all major changes since the last release
- [ ] A draft release should be created. Copy the release notes text from the template at [`docs/templates/release-notes`](https://github.com/freedomofpress/dangerzone/tree/main/docs/templates/)
@ -46,6 +47,12 @@ In case of the removal of a version:
* Consult the previous paragraph, but also `grep` your way around.
2. Add a notice in our `CHANGELOG.md` about the version removal.
## Bump the minimum Docker Desktop version
We embed the minimum docker desktop versions inside Dangerzone, as an incentive for our macOS and Windows users to upgrade to the latests version.
You can find the latest version at the time of the release by looking at [their release notes](https://docs.docker.com/desktop/release-notes/)
## Large Document Testing
Parallel to the QA process, the release candidate should be put through the large document tests in a dedicated machine to run overnight.

View file

@ -124,6 +124,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.setWindowTitle("Dangerzone")
self.setWindowIcon(self.dangerzone.get_window_icon())
self.alert: Optional[Alert] = None
self.setMinimumWidth(600)
if platform.system() == "Darwin":
@ -226,6 +227,13 @@ class MainWindow(QtWidgets.QMainWindow):
# This allows us to make QSS rules conditional on the OS color mode.
self.setProperty("OSColorMode", self.dangerzone.app.os_color_mode.value)
if hasattr(self.dangerzone.isolation_provider, "check_docker_desktop_version"):
is_version_valid, version = (
self.dangerzone.isolation_provider.check_docker_desktop_version()
)
if not is_version_valid:
self.handle_docker_desktop_version_check(is_version_valid, version)
self.show()
def show_update_success(self) -> None:
@ -279,6 +287,46 @@ class MainWindow(QtWidgets.QMainWindow):
self.dangerzone.settings.set("updater_check", check)
self.dangerzone.settings.save()
def handle_docker_desktop_version_check(
self, is_version_valid: bool, version: str
) -> None:
hamburger_menu = self.hamburger_button.menu()
sep = hamburger_menu.insertSeparator(hamburger_menu.actions()[0])
upgrade_action = QAction("Docker Desktop should be upgraded", hamburger_menu)
upgrade_action.setIcon(
QtGui.QIcon(
load_svg_image(
"hamburger_menu_update_dot_error.svg", width=64, height=64
)
)
)
message = """
<p>A new version of Docker Desktop is available. Please upgrade your system.</p>
<p>Visit the <a href="https://www.docker.com/products/docker-desktop">Docker Desktop website</a> to download the latest version.</p>
<em>Keeping Docker Desktop up to date allows you to have more confidence that your documents are processed safely.</em>
"""
self.alert = Alert(
self.dangerzone,
title="Upgrade Docker Desktop",
message=message,
ok_text="Ok",
has_cancel=False,
)
def _launch_alert() -> None:
if self.alert:
self.alert.launch()
upgrade_action.triggered.connect(_launch_alert)
hamburger_menu.insertAction(sep, upgrade_action)
self.hamburger_button.setIcon(
QtGui.QIcon(
load_svg_image("hamburger_menu_update_error.svg", width=64, height=64)
)
)
def handle_updates(self, report: UpdateReport) -> None:
"""Handle update reports from the update checker thread.
@ -365,7 +413,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.content_widget.show()
def closeEvent(self, e: QtGui.QCloseEvent) -> None:
alert_widget = Alert(
self.alert = Alert(
self.dangerzone,
message="Some documents are still being converted.\n Are you sure you want to quit?",
ok_text="Abort conversions",
@ -379,7 +427,7 @@ class MainWindow(QtWidgets.QMainWindow):
else:
self.dangerzone.app.exit(0)
else:
accept_exit = alert_widget.launch()
accept_exit = self.alert.launch()
if not accept_exit:
e.ignore()
return
@ -623,7 +671,7 @@ class ContentWidget(QtWidgets.QWidget):
def documents_selected(self, docs: List[Document]) -> None:
if self.conversion_started:
Alert(
self.alert = Alert(
self.dangerzone,
message="Dangerzone does not support adding documents after the conversion has started.",
has_cancel=False,
@ -633,7 +681,7 @@ class ContentWidget(QtWidgets.QWidget):
# Ensure all files in batch are in the same directory
dirnames = {os.path.dirname(doc.input_filename) for doc in docs}
if len(dirnames) > 1:
Alert(
self.alert = Alert(
self.dangerzone,
message="Dangerzone does not support adding documents from multiple locations.\n\n The newly added documents were ignored.",
has_cancel=False,
@ -802,14 +850,14 @@ class DocSelectionDropFrame(QtWidgets.QFrame):
text = f"{num_unsupported_docs} files are not supported."
ok_text = "Continue without these files"
alert_widget = Alert(
self.alert = Alert(
self.dangerzone,
message=f"{text}\nThe supported extensions are: "
+ ", ".join(get_supported_extensions()),
ok_text=ok_text,
)
return alert_widget.exec_()
return self.alert.exec_()
class SettingsWidget(QtWidgets.QWidget):

View file

@ -3,7 +3,7 @@ import os
import platform
import shlex
import subprocess
from typing import List
from typing import List, Tuple
from .. import container_utils, errors
from ..document import Document
@ -11,7 +11,10 @@ from ..util import get_resource_path, get_subprocess_startupinfo
from .base import IsolationProvider, terminate_process_group
TIMEOUT_KILL = 5 # Timeout in seconds until the kill command returns.
MINIMUM_DOCKER_DESKTOP = {
"Darwin": "4.36.0",
"Windows": "4.36.0",
}
# Define startupinfo for subprocesses
if platform.system() == "Windows":
@ -121,6 +124,7 @@ class Container(IsolationProvider):
def is_available() -> bool:
container_runtime = container_utils.get_runtime()
runtime_name = container_utils.get_runtime_name()
# Can we run `docker/podman image ls` without an error
with subprocess.Popen(
[container_runtime, "image", "ls"],
@ -135,6 +139,28 @@ class Container(IsolationProvider):
)
return True
def check_docker_desktop_version(self) -> Tuple[bool, str]:
# On windows and darwin, check that the minimum version is met
version = ""
if platform.system() != "Linux":
with subprocess.Popen(
["docker", "version", "--format", "{{.Server.Platform.Name}}"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
startupinfo=get_subprocess_startupinfo(),
) as p:
stdout, stderr = p.communicate()
if p.returncode != 0:
# In the case where there were an error, consider that
# the check went trough, as we're checking for installation
# compatibiliy somewhere else already
return True, version
# The output is like "Docker Desktop 4.35.1 (173168)"
version = stdout.decode().replace("Docker Desktop", "").split()[0]
if version < MINIMUM_DOCKER_DESKTOP[platform.system()]:
return False, version
return True, version
def doc_to_pixels_container_name(self, document: Document) -> str:
"""Unique container name for the doc-to-pixels phase."""
return f"dangerzone-doc-to-pixels-{document.id}"

View file

@ -587,3 +587,57 @@ def test_installation_failure_return_false(qtbot: QtBot, mocker: MockerFixture)
assert "the following error occured" in widget.label.text()
assert "The image cannot be found" in widget.traceback.toPlainText()
def test_up_to_date_docker_desktop_does_nothing(
qtbot: QtBot, mocker: MockerFixture
) -> None:
# Setup install to return False
mock_app = mocker.MagicMock()
dummy = mocker.MagicMock(spec=Container)
dummy.check_docker_desktop_version.return_value = (True, "1.0.0")
dz = DangerzoneGui(mock_app, dummy)
window = MainWindow(dz)
qtbot.addWidget(window)
menu_actions = window.hamburger_button.menu().actions()
assert "Docker Desktop should be upgraded" not in [
a.toolTip() for a in menu_actions
]
def test_outdated_docker_desktop_displays_warning(
qtbot: QtBot, mocker: MockerFixture
) -> None:
# Setup install to return False
mock_app = mocker.MagicMock()
dummy = mocker.MagicMock(spec=Container)
dummy.check_docker_desktop_version.return_value = (False, "1.0.0")
dz = DangerzoneGui(mock_app, dummy)
load_svg_spy = mocker.spy(main_window_module, "load_svg_image")
window = MainWindow(dz)
qtbot.addWidget(window)
menu_actions = window.hamburger_button.menu().actions()
assert menu_actions[0].toolTip() == "Docker Desktop should be upgraded"
# Check that the hamburger icon has changed with the expected SVG image.
assert load_svg_spy.call_count == 4
assert (
load_svg_spy.call_args_list[2].args[0] == "hamburger_menu_update_dot_error.svg"
)
alert_spy = mocker.spy(window.alert, "launch")
# Clicking the menu item should open a warning message
def _check_alert_displayed() -> None:
alert_spy.assert_any_call()
if window.alert:
window.alert.close()
QtCore.QTimer.singleShot(0, _check_alert_displayed)
menu_actions[0].trigger()

View file

@ -1,4 +1,5 @@
import os
import platform
import pytest
from pytest_mock import MockerFixture
@ -108,6 +109,92 @@ class TestContainer(IsolationProviderTest):
with pytest.raises(errors.ImageNotPresentException):
provider.install()
@pytest.mark.skipif(
platform.system() not in ("Windows", "Darwin"),
reason="macOS and Windows specific",
)
def test_old_docker_desktop_version_is_detected(
self, mocker: MockerFixture, provider: Container, fp: FakeProcess
) -> None:
fp.register_subprocess(
[
"docker",
"version",
"--format",
"{{.Server.Platform.Name}}",
],
stdout="Docker Desktop 1.0.0 (173100)",
)
mocker.patch(
"dangerzone.isolation_provider.container.MINIMUM_DOCKER_DESKTOP",
{"Darwin": "1.0.1", "Windows": "1.0.1"},
)
assert (False, "1.0.0") == provider.check_docker_desktop_version()
@pytest.mark.skipif(
platform.system() not in ("Windows", "Darwin"),
reason="macOS and Windows specific",
)
def test_up_to_date_docker_desktop_version_is_detected(
self, mocker: MockerFixture, provider: Container, fp: FakeProcess
) -> None:
fp.register_subprocess(
[
"docker",
"version",
"--format",
"{{.Server.Platform.Name}}",
],
stdout="Docker Desktop 1.0.1 (173100)",
)
# Require version 1.0.1
mocker.patch(
"dangerzone.isolation_provider.container.MINIMUM_DOCKER_DESKTOP",
{"Darwin": "1.0.1", "Windows": "1.0.1"},
)
assert (True, "1.0.1") == provider.check_docker_desktop_version()
fp.register_subprocess(
[
"docker",
"version",
"--format",
"{{.Server.Platform.Name}}",
],
stdout="Docker Desktop 2.0.0 (173100)",
)
assert (True, "2.0.0") == provider.check_docker_desktop_version()
@pytest.mark.skipif(
platform.system() not in ("Windows", "Darwin"),
reason="macOS and Windows specific",
)
def test_docker_desktop_version_failure_returns_true(
self, mocker: MockerFixture, provider: Container, fp: FakeProcess
) -> None:
fp.register_subprocess(
[
"docker",
"version",
"--format",
"{{.Server.Platform.Name}}",
],
stderr="Oopsie",
returncode=1,
)
assert provider.check_docker_desktop_version() == (True, "")
@pytest.mark.skipif(
platform.system() != "Linux",
reason="Linux specific",
)
def test_linux_skips_desktop_version_check_returns_true(
self, mocker: MockerFixture, provider: Container
) -> None:
assert (True, "") == provider.check_docker_desktop_version()
class TestContainerTermination(IsolationProviderTermination):
pass