Compare commits

..

17 commits

Author SHA1 Message Date
Alex Pyrgiotis
61ce5520b0
Merge 103c66c1bd into 60df4f7e35 2024-12-05 10:50:42 +02:00
Alex Pyrgiotis
103c66c1bd
FIXUP: Remove unnecessary container function 2024-12-05 10:50:34 +02:00
Alex Pyrgiotis
7b1d175640
fixup! ci: Work with image tarballs that are not tagged as 'latest' 2024-12-04 18:35:21 +02:00
Alex Pyrgiotis
767617d21c
FIXUP: Formatting 2024-12-04 18:14:28 +02:00
Alex Pyrgiotis
bd72b6a93b
ci: Work with image tarballs that are not tagged as 'latest'
Now that our image tarball is not tagged as 'latest', we must first grab
the image tag first, and then refer to it. We can grab the tag either
from `share/image-id.txt` (if available) or with:

    docker load dangerzone.rocks/dangerzone --format {{ .Tag }}
2024-12-04 18:11:44 +02:00
Alex Pyrgiotis
c0fa32b6b8
FIXUP: Don't require a 'latest' image tag anymore 2024-12-04 18:11:44 +02:00
Alex Pyrgiotis
2f438c09f1
FIXUP: Use longer tag description, so that the commit is always shown 2024-12-04 17:37:45 +02:00
Alex Pyrgiotis
eefe7c15ce
Move container security arg to proper place
Now that #748 has been merged, we can move the `--userns nomap` argument
to the list with the rest of our security arguments.
2024-12-04 17:36:38 +02:00
Alex Pyrgiotis
e7cd6e3138
Factor out container utilities to separate module 2024-12-04 17:30:34 +02:00
Alex Pyrgiotis
9b244b8d83
Extend the interface of the isolation provider
Add the following two methods in the isolation provider:
1. `.is_available()`: Mainly used for the Container isolation provider,
   it specifies whether the container runtime is up and running. May be
   used in the future by other similar providers.
2. `.should_wait_install()`: Whether the isolation provider takes a
   while to be installed. Should be `True` only for the Container
   isolation provider, for the time being.
2024-12-04 16:50:27 +02:00
Alex Pyrgiotis
ca63d571c7
Fix minor typos in our docs 2024-12-04 16:37:35 +02:00
Alex Pyrgiotis
e51407ef50
Update our release instructions 2024-12-04 16:37:21 +02:00
Alex Pyrgiotis
5b1fe4d7ad
container: Revamp container image installation
Revamp the container image installation process in a way that does not
involve using image IDs. We don't want to rely on image IDs anymore,
since they are brittle (see
https://github.com/freedomofpress/dangerzone/issues/933). Instead, we
use image tags, as provided in the `image-id.txt` file.  This allows us
to check fast if an image is up to date, and we no longer need to
maintain multiple image IDs from various container runtimes.

Refs #933
Refs #988
Fixes #1020
2024-12-04 06:28:27 +02:00
Alex Pyrgiotis
53214d33d8
Build and tag Dangerzone images
Build Dangerzone images and tag them with a unique ID that stems from
the Git reop. Note that using tags as image IDs instead of regular image
IDs breaks the current Dangerzone expectations, but this will be
addressed in subsequent commits.
2024-12-04 06:28:27 +02:00
Alex Pyrgiotis
7f7fe43711
container: Factor out loading an image tarball 2024-12-04 06:28:27 +02:00
Alex Pyrgiotis
f31fbfefc6
container: Manipulate Dangerzone image tags
Add the following methods that allow the `Container` isolation provider
to work with tags for the Dangerzone image:
* `list_image_tag()`
* `delete_image_tag()`
* `add_image_tag()`
2024-12-04 06:28:27 +02:00
Alex Pyrgiotis
96e64deae7
Move container-specific method from base class
Move the `is_runtime_available()` method from the base
`IsolationProvider` class, and into the `Dummy` provider class. This
method was originally defined in the base class, in order to be mocked
in our tests for the `Dummy` provider. There's no reason for the `Qubes`
class to have it though, so we can just move it to the `Dummy` provider.
2024-12-04 06:28:27 +02:00
5 changed files with 161 additions and 213 deletions

View file

@ -121,14 +121,10 @@ jobs:
key: v1-tessdata-${{ hashFiles('./install/common/download-tessdata.py') }} key: v1-tessdata-${{ hashFiles('./install/common/download-tessdata.py') }}
- name: Run CLI tests - name: Run CLI tests
run: poetry run make test run: poetry run make test
- name: Set up .NET CLI environment # Taken from: https://github.com/orgs/community/discussions/27149#discussioncomment-3254829
uses: actions/setup-dotnet@v4 - name: Set path for candle and light
with: run: echo "C:\Program Files (x86)\WiX Toolset v3.14\bin" >> $GITHUB_PATH
dotnet-version: "8.x" shell: bash
- name: Install WiX Toolset
run: dotnet tool install --global wix
- name: Add WiX UI extension
run: wix extension add --global WixToolset.UI.wixext
- name: Build the MSI installer - name: Build the MSI installer
# NOTE: This also builds the .exe internally. # NOTE: This also builds the .exe internally.
run: poetry run .\install\windows\build-app.bat run: poetry run .\install\windows\build-app.bat

View file

@ -471,24 +471,11 @@ poetry shell
.\dev_scripts\dangerzone.bat .\dev_scripts\dangerzone.bat
``` ```
### If you want to build the Windows installer ### If you want to build the installer
Install [.NET SDK](https://dotnet.microsoft.com/en-us/download) version 6 or later. Then, open a terminal and install the latest version of [WiX Toolset .NET tool](https://wixtoolset.org/) **v5** with: * Go to https://dotnet.microsoft.com/download/dotnet-framework and download and install .NET Framework 3.5 SP1 Runtime. I downloaded `dotnetfx35.exe`.
* Go to https://wixtoolset.org/releases/ and download and install WiX toolset. I downloaded `wix314.exe`.
```sh * Add `C:\Program Files (x86)\WiX Toolset v3.14\bin` to the path ([instructions](https://web.archive.org/web/20230221104142/https://windowsloop.com/how-to-add-to-windows-path/)).
dotnet tool install --global wix --version 5.*
```
Install the WiX UI extension. You may need to open a new terminal in order to use the newly installed `wix` .NET tool:
```sh
wix extension add --global WixToolset.UI.wixext/5.x.y
```
> [!IMPORTANT]
> To avoid compatibility issues, ensure the WiX UI extension version matches the version of the WiX Toolset.
>
> Run `wix --version` to check the version of WiX Toolset you have installed and replace `5.x.y` with the full version number without the Git revision.
### If you want to sign binaries with Authenticode ### If you want to sign binaries with Authenticode

View file

@ -16,11 +16,6 @@ since 0.4.1, and this project adheres to [Semantic Versioning](https://semver.or
- Platform support: Drop support for Fedora 39, since it's end-of-life ([#999](https://github.com/freedomofpress/dangerzone/pull/999)) - Platform support: Drop support for Fedora 39, since it's end-of-life ([#999](https://github.com/freedomofpress/dangerzone/pull/999))
### Development changes
- Build Dangerzone MSI with Wix Toolset 5 ([#929](https://github.com/freedomofpress/dangerzone/pull/929)).
Thanks [@jkarasti](https://github.com/jkarasti) for the contribution.
## [0.8.0](https://github.com/freedomofpress/dangerzone/compare/v0.8.0...0.7.1) ## [0.8.0](https://github.com/freedomofpress/dangerzone/compare/v0.8.0...0.7.1)
### Added ### Added

View file

@ -17,23 +17,22 @@ signtool.exe sign /v /d "Dangerzone" /a /n "Freedom of the Press Foundation" /fd
REM verify the signature of dangerzone-cli.exe REM verify the signature of dangerzone-cli.exe
signtool.exe verify /pa build\exe.win-amd64-3.12\dangerzone-cli.exe signtool.exe verify /pa build\exe.win-amd64-3.12\dangerzone-cli.exe
REM build the wxs file REM build the wix file
python install\windows\build-wxs.py python install\windows\build-wxs.py > build\Dangerzone.wxs
REM build the msi package REM build the msi package
cd build cd build
wix build -arch x64 -ext WixToolset.UI.wixext .\Dangerzone.wxs -out Dangerzone.msi candle.exe Dangerzone.wxs
light.exe -ext WixUIExtension Dangerzone.wixobj
REM validate Dangerzone.msi
wix msi validate Dangerzone.msi
REM code sign Dangerzone.msi REM code sign Dangerzone.msi
insignia.exe -im Dangerzone.msi
signtool.exe sign /v /d "Dangerzone" /a /n "Freedom of the Press Foundation" /fd sha256 /t http://time.certum.pl/ Dangerzone.msi signtool.exe sign /v /d "Dangerzone" /a /n "Freedom of the Press Foundation" /fd sha256 /t http://time.certum.pl/ Dangerzone.msi
REM verify the signature of Dangerzone.msi REM verify the signature of Dangerzone.msi
signtool.exe verify /pa Dangerzone.msi signtool.exe verify /pa Dangerzone.msi
REM move Dangerzone.msi to dist REM moving Dangerzone.msi to dist
cd .. cd ..
mkdir dist mkdir dist
move build\Dangerzone.msi dist move build\Dangerzone.msi dist

View file

@ -4,75 +4,114 @@ import uuid
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
def build_data(base_path, path_prefix, dir_id, dir_name): def build_data(dirname, dir_prefix, id_, name):
data = { data = {
"directory_name": dir_name, "id": id_,
"directory_id": dir_id, "name": name,
"files": [], "files": [],
"dirs": [], "dirs": [],
} }
if dir_id == "INSTALLFOLDER": for basename in os.listdir(dirname):
data["component_id"] = "ApplicationFiles" filename = os.path.join(dirname, basename)
else: if os.path.isfile(filename):
data["component_id"] = "Component" + dir_id data["files"].append(os.path.join(dir_prefix, basename))
data["component_guid"] = str(uuid.uuid4()).upper() elif os.path.isdir(filename):
if id_ == "INSTALLDIR":
for entry in os.listdir(base_path): id_prefix = "Folder"
entry_path = os.path.join(base_path, entry)
if os.path.isfile(entry_path):
data["files"].append(os.path.join(path_prefix, entry))
elif os.path.isdir(entry_path):
if dir_id == "INSTALLFOLDER":
next_dir_prefix = "Folder"
else: else:
next_dir_prefix = dir_id id_prefix = id_
# Skip lib/PySide6/examples folder due to ilegal file names # Skip lib/PySide6/examples folder due to ilegal file names
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\examples" in base_path: if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\examples" in dirname:
continue continue
# Skip lib/PySide6/qml/QtQuick folder due to ilegal file names # Skip lib/PySide6/qml/QtQuick folder due to ilegal file names
# XXX Since we're not using Qml it should be no problem # XXX Since we're not using Qml it should be no problem
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\qml\\QtQuick" in base_path: if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\qml\\QtQuick" in dirname:
continue continue
next_dir_id = next_dir_prefix + entry.capitalize().replace("-", "_") id_value = f"{id_prefix}{basename.capitalize().replace('-', '_')}"
subdata = build_data( data["dirs"].append(
os.path.join(base_path, entry), build_data(
os.path.join(path_prefix, entry), os.path.join(dirname, basename),
next_dir_id, os.path.join(dir_prefix, basename),
entry, id_value,
basename,
)
) )
# Add the subdirectory only if it contains files or subdirectories if len(data["files"]) > 0:
if subdata["files"] or subdata["dirs"]: if id_ == "INSTALLDIR":
data["dirs"].append(subdata) data["component_id"] = "ApplicationFiles"
else:
data["component_id"] = "FolderComponent" + id_[len("Folder") :]
data["component_guid"] = str(uuid.uuid4())
return data return data
def build_directory_xml(root, data): def build_dir_xml(root, data):
attrs = {} attrs = {}
attrs["Id"] = data["directory_id"] if "id" in data:
attrs["Name"] = data["directory_name"] attrs["Id"] = data["id"]
directory_el = ET.SubElement(root, "Directory", attrs) if "name" in data:
attrs["Name"] = data["name"]
el = ET.SubElement(root, "Directory", attrs)
for subdata in data["dirs"]: for subdata in data["dirs"]:
build_directory_xml(directory_el, subdata) build_dir_xml(el, subdata)
# If this is the ProgramMenuFolder, add the menu component
if "id" in data and data["id"] == "ProgramMenuFolder":
component_el = ET.SubElement(
el,
"Component",
Id="ApplicationShortcuts",
Guid="539e7de8-a124-4c09-aa55-0dd516aad7bc",
)
ET.SubElement(
component_el,
"Shortcut",
Id="ApplicationShortcut1",
Name="Dangerzone",
Description="Dangerzone",
Target="[INSTALLDIR]dangerzone.exe",
WorkingDirectory="INSTALLDIR",
)
ET.SubElement(
component_el,
"RegistryValue",
Root="HKCU",
Key="Software\Freedom of the Press Foundation\Dangerzone",
Name="installed",
Type="integer",
Value="1",
KeyPath="yes",
)
def build_components_xml(root, data): def build_components_xml(root, data):
component_el = ET.SubElement( component_ids = []
root, if "component_id" in data:
"Component", component_ids.append(data["component_id"])
Id=data["component_id"],
Guid=data["component_guid"],
Directory=data["directory_id"],
)
for filename in data["files"]:
ET.SubElement(component_el, "File", Source=filename)
for subdata in data["dirs"]: for subdata in data["dirs"]:
build_components_xml(root, subdata) if "component_guid" in subdata:
dir_ref_el = ET.SubElement(root, "DirectoryRef", Id=subdata["id"])
component_el = ET.SubElement(
dir_ref_el,
"Component",
Id=subdata["component_id"],
Guid=subdata["component_guid"],
)
for filename in subdata["files"]:
file_el = ET.SubElement(
component_el, "File", Source=filename, Id="file_" + uuid.uuid4().hex
)
component_ids += build_components_xml(root, subdata)
return component_ids
def main(): def main():
@ -86,188 +125,120 @@ def main():
# -rc markers. # -rc markers.
version = f.read().strip().split("-")[0] version = f.read().strip().split("-")[0]
build_dir = os.path.join( dist_dir = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
"build", "build",
"exe.win-amd64-3.12",
) )
cx_freeze_dir = "exe.win-amd64-3.12"
dist_dir = os.path.join(build_dir, cx_freeze_dir)
if not os.path.exists(dist_dir): if not os.path.exists(dist_dir):
print("You must build the dangerzone binary before running this") print("You must build the dangerzone binary before running this")
return return
# Prepare data for WiX file harvesting from the output of cx_Freeze data = {
data = build_data( "id": "TARGETDIR",
dist_dir, "name": "SourceDir",
cx_freeze_dir, "dirs": [
"INSTALLFOLDER", {
"Dangerzone", "id": "ProgramFilesFolder",
"dirs": [],
},
{
"id": "ProgramMenuFolder",
"dirs": [],
},
],
}
data["dirs"][0]["dirs"].append(
build_data(
dist_dir,
"exe.win-amd64-3.12",
"INSTALLDIR",
"Dangerzone",
)
) )
# Add the Wix root element root_el = ET.Element("Wix", xmlns="http://schemas.microsoft.com/wix/2006/wi")
wix_el = ET.Element( product_el = ET.SubElement(
"Wix", root_el,
{ "Product",
"xmlns": "http://wixtoolset.org/schemas/v4/wxs",
"xmlns:ui": "http://wixtoolset.org/schemas/v4/wxs/ui",
},
)
# Add the Package element
package_el = ET.SubElement(
wix_el,
"Package",
Name="Dangerzone", Name="Dangerzone",
Manufacturer="Freedom of the Press Foundation", Manufacturer="Freedom of the Press Foundation",
UpgradeCode="12B9695C-965B-4BE0-BC33-21274E809576", Id="*",
UpgradeCode="$(var.ProductUpgradeCode)",
Language="1033", Language="1033",
Compressed="yes",
Codepage="1252", Codepage="1252",
Version=version, Version="$(var.ProductVersion)",
) )
ET.SubElement( ET.SubElement(
package_el, product_el,
"SummaryInformation", "Package",
Id="*",
Keywords="Installer", Keywords="Installer",
Description="Dangerzone " + version + " Installer", Description="Dangerzone $(var.ProductVersion) Installer",
Codepage="1252", Manufacturer="Freedom of the Press Foundation",
InstallerVersion="100",
Languages="1033",
Compressed="yes",
SummaryCodepage="1252",
) )
ET.SubElement(package_el, "MediaTemplate", EmbedCab="yes") ET.SubElement(product_el, "Media", Id="1", Cabinet="product.cab", EmbedCab="yes")
ET.SubElement( ET.SubElement(
package_el, "Icon", Id="ProductIcon", SourceFile="..\\share\\dangerzone.ico" product_el, "Icon", Id="ProductIcon", SourceFile="..\\share\\dangerzone.ico"
) )
ET.SubElement(package_el, "Property", Id="ARPPRODUCTICON", Value="ProductIcon") ET.SubElement(product_el, "Property", Id="ARPPRODUCTICON", Value="ProductIcon")
ET.SubElement( ET.SubElement(
package_el, product_el,
"Property", "Property",
Id="ARPHELPLINK", Id="ARPHELPLINK",
Value="https://dangerzone.rocks", Value="https://dangerzone.rocks",
) )
ET.SubElement( ET.SubElement(
package_el, product_el,
"Property", "Property",
Id="ARPURLINFOABOUT", Id="ARPURLINFOABOUT",
Value="https://freedom.press", Value="https://freedom.press",
) )
ET.SubElement( ET.SubElement(
package_el, "ui:WixUI", Id="WixUI_InstallDir", InstallDirectory="INSTALLFOLDER" product_el,
"Property",
Id="WIXUI_INSTALLDIR",
Value="INSTALLDIR",
) )
ET.SubElement(package_el, "UIRef", Id="WixUI_ErrorProgressText") ET.SubElement(product_el, "UIRef", Id="WixUI_InstallDir")
ET.SubElement(product_el, "UIRef", Id="WixUI_ErrorProgressText")
ET.SubElement( ET.SubElement(
package_el, product_el,
"WixVariable", "WixVariable",
Id="WixUILicenseRtf", Id="WixUILicenseRtf",
Value="..\\install\\windows\\license.rtf", Value="..\\install\\windows\\license.rtf",
) )
ET.SubElement( ET.SubElement(
package_el, product_el,
"WixVariable", "WixVariable",
Id="WixUIDialogBmp", Id="WixUIDialogBmp",
Value="..\\install\\windows\\dialog.bmp", Value="..\\install\\windows\\dialog.bmp",
) )
ET.SubElement( ET.SubElement(
package_el, product_el,
"MajorUpgrade", "MajorUpgrade",
AllowSameVersionUpgrades="yes",
DowngradeErrorMessage="A newer version of [ProductName] is already installed. If you are sure you want to downgrade, remove the existing installation via Programs and Features.", DowngradeErrorMessage="A newer version of [ProductName] is already installed. If you are sure you want to downgrade, remove the existing installation via Programs and Features.",
) )
# Workaround for an issue after upgrading from WiX Toolset v3 to v5 where the previous build_dir_xml(product_el, data)
# version of Dangerzone is not uninstalled during the upgrade by checking if the older installation component_ids = build_components_xml(product_el, data)
# exists in "C:\Program Files (x86)\Dangerzone".
#
# Also handle a special case for Dangerzone 0.8.0 which allows choosing the install location
# during install by checking if the registry key for it exists.
#
# Note that this seems to allow installing Dangerzone 0.8.0 after installing Dangerzone from this branch.
# In this case the installer errors until Dangerzone 0.8.0 is uninstalled again
#
# TODO: Revert this once we are reasonably certain there aren't too many affected Dangerzone installations.
find_old_el = ET.SubElement(package_el, "Property", Id="OLDDANGERZONEFOUND")
directory_search_el = ET.SubElement(
find_old_el,
"DirectorySearch",
Id="dangerzone_install_folder",
Path="C:\\Program Files (x86)\\Dangerzone",
)
ET.SubElement(directory_search_el, "FileSearch", Name="dangerzone.exe")
registry_search_el = ET.SubElement(package_el, "Property", Id="DANGERZONE080FOUND")
ET.SubElement(
registry_search_el,
"RegistrySearch",
Root="HKLM",
Key="SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{03C2D2B2-9955-4AED-831F-DA4E67FC0FDB}",
Name="DisplayName",
Type="raw",
)
ET.SubElement(
package_el,
"Launch",
Condition="NOT OLDDANGERZONEFOUND AND NOT DANGERZONE080FOUND",
Message="A previous version of [ProductName] is already installed. Please uninstall it from Programs and Features before proceeding with the installation.",
)
# Add the ProgramMenuFolder StandardDirectory feature_el = ET.SubElement(product_el, "Feature", Id="DefaultFeature", Level="1")
programmenufolder_el = ET.SubElement( for component_id in component_ids:
package_el, ET.SubElement(feature_el, "ComponentRef", Id=component_id)
"StandardDirectory",
Id="ProgramMenuFolder",
)
# Add a shortcut for Dangerzone in the Start menu
shortcut_el = ET.SubElement(
programmenufolder_el,
"Component",
Id="ApplicationShortcuts",
Guid="539E7DE8-A124-4C09-AA55-0DD516AAD7BC",
)
ET.SubElement(
shortcut_el,
"Shortcut",
Id="DangerzoneStartMenuShortcut",
Name="Dangerzone",
Description="Dangerzone",
Target="[INSTALLFOLDER]dangerzone.exe",
WorkingDirectory="INSTALLFOLDER",
)
ET.SubElement(
shortcut_el,
"RegistryValue",
Root="HKCU",
Key="Software\\Freedom of the Press Foundation\\Dangerzone",
Name="installed",
Type="integer",
Value="1",
KeyPath="yes",
)
# Add the ProgramFilesFolder StandardDirectory
programfilesfolder_el = ET.SubElement(
package_el,
"StandardDirectory",
Id="ProgramFiles64Folder",
)
# Create the directory structure for the installed product
build_directory_xml(programfilesfolder_el, data)
# Create a component group for application components
applicationcomponents_el = ET.SubElement(
package_el, "ComponentGroup", Id="ApplicationComponents"
)
# Populate the application components group with components for the installed package
build_components_xml(applicationcomponents_el, data)
# Add the Feature element
feature_el = ET.SubElement(package_el, "Feature", Id="DefaultFeature", Level="1")
ET.SubElement(feature_el, "ComponentGroupRef", Id="ApplicationComponents")
ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts") ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts")
ET.indent(wix_el, space=" ") print('<?xml version="1.0" encoding="windows-1252"?>')
print(f'<?define ProductVersion = "{version}"?>')
with open(os.path.join(build_dir, "Dangerzone.wxs"), "w") as wxs_file: print('<?define ProductUpgradeCode = "12b9695c-965b-4be0-bc33-21274e809576"?>')
wxs_file.write(ET.tostring(wix_el).decode()) ET.indent(root_el)
print(ET.tostring(root_el).decode())
if __name__ == "__main__": if __name__ == "__main__":