Merge pull request #1519 from safing/feature/tauri-beta-update

Feature Tauri beta update
This commit is contained in:
Daniel Hååvi 2024-07-19 16:11:31 +02:00 committed by GitHub
commit ef0085882d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
57 changed files with 15992 additions and 8993 deletions

View file

@ -56,4 +56,4 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build angular projects - name: Build angular projects
run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +angular-release run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +angular-ci

View file

@ -33,4 +33,4 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Kernel Extension - name: Build Kernel Extension
run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +kext-build run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +kext-ci

View file

@ -33,4 +33,4 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build tauri project - name: Build tauri project
run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +tauri-release run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +tauri-ci

386
Earthfile
View file

@ -12,24 +12,90 @@ ARG --global work_image = "alpine"
ARG --global outputDir = "./dist" ARG --global outputDir = "./dist"
# Architectures:
# The list of rust targets we support. They will be automatically converted # The list of rust targets we support. They will be automatically converted
# to GOOS, GOARCH and GOARM when building go binaries. See the +RUST_TO_GO_ARCH_STRING # to GOOS, GOARCH and GOARM when building go binaries. See the +RUST_TO_GO_ARCH_STRING
# helper method at the bottom of the file. # helper method at the bottom of the file.
#
ARG --global architectures = "x86_64-unknown-linux-gnu" \ # Linux:
"aarch64-unknown-linux-gnu" \ # x86_64-unknown-linux-gnu
"x86_64-pc-windows-gnu" # aarch64-unknown-linux-gnu
# TODO: Compile errors here: # armv7-unknown-linux-gnueabihf
# "aarch64-pc-windows-gnu" \ # arm-unknown-linux-gnueabi
# "x86_64-apple-darwin" \ #
# "aarch64-apple-darwin" # Windows:
# "armv7-unknown-linux-gnueabihf" \ # x86_64-pc-windows-gnu
# "arm-unknown-linux-gnueabi" # aarch64-pc-windows-gnu
#
# Mac:
# x86_64-apple-darwin
# aarch64-apple-darwin
# Import the earthly rust lib since it already provides some useful # Import the earthly rust lib since it already provides some useful
# build-targets and methods to initialize the rust toolchain. # build-targets and methods to initialize the rust toolchain.
IMPORT github.com/earthly/lib/rust:3.0.2 AS rust IMPORT github.com/earthly/lib/rust:3.0.2 AS rust
build:
# Build all Golang binaries:
# ./dist/linux_amd64/portmaster-core
# ./dist/linux_amd64/portmaster-start
# ./dist/linux_arm64/portmaster-core
# ./dist/linux_arm64/portmaster-start
# ./dist/windows_amd64/portmaster-core.exe
# ./dist/windows_amd64/portmaster-start.exe
# ./dist/windows_arm64/portmaster-core.exe
# ./dist/windows_arm64/portmaster-start.exe
BUILD +go-build --GOOS="linux" --GOARCH="amd64"
BUILD +go-build --GOOS="linux" --GOARCH="arm64"
BUILD +go-build --GOOS="windows" --GOARCH="amd64"
BUILD +go-build --GOOS="windows" --GOARCH="arm64"
# Build the Angular UI:
# ./dist/all/portmaster-ui.zip
BUILD +angular-release
# Build Tauri app binaries:
# ./dist/linux_amd64/portmaster-app
# ./dist/linux_amd64/Portmaster-0.1.0-1.x86_64.rpm
# ./dist/linux_amd64/Portmaster_0.1.0_amd64.deb
BUILD +tauri-build --target="x86_64-unknown-linux-gnu"
# TODO:
# BUILD +tauri-build --target="x86_64-pc-windows-gnu"
# Bild Tauri release bundle for Windows:
# ./dist/windows_amd64/portmaster-app_vX-X-X.zip
BUILD +tauri-windows-release-bundle
# Build UI assets:
# ./dist/all/assets.zip
BUILD +assets
go-ci:
BUILD +go-build --GOOS="linux" --GOARCH="amd64"
BUILD +go-build --GOOS="linux" --GOARCH="arm64"
BUILD +go-build --GOOS="windows" --GOARCH="amd64"
BUILD +go-build --GOOS="windows" --GOARCH="arm64"
BUILD +go-test
angular-ci:
BUILD +angular-release
tauri-ci:
BUILD +tauri-build --target="x86_64-unknown-linux-gnu"
BUILD +tauri-windows-release-bundle
kext-ci:
BUILD +kext-build
release:
LOCALLY
IF ! git diff --quiet
RUN echo -e "\033[1;31m Refusing to release a dirty git repository. Please commit your local changes first! \033[0m" ; exit 1
END
BUILD +build
go-deps: go-deps:
FROM ${go_builder_image} FROM ${go_builder_image}
WORKDIR /go-workdir WORKDIR /go-workdir
@ -111,7 +177,7 @@ go-build:
ARG GOOS=linux ARG GOOS=linux
ARG GOARCH=amd64 ARG GOARCH=amd64
ARG GOARM ARG GOARM
ARG CMDS=portmaster-start portmaster-core hub notifier ARG CMDS=portmaster-start portmaster-core
CACHE --sharing shared "$GOCACHE" CACHE --sharing shared "$GOCACHE"
CACHE --sharing shared "$GOMODCACHE" CACHE --sharing shared "$GOMODCACHE"
@ -182,6 +248,7 @@ go-test:
go-test-all: go-test-all:
FROM ${work_image} FROM ${work_image}
ARG --required architectures
FOR arch IN ${architectures} FOR arch IN ${architectures}
DO +RUST_TO_GO_ARCH_STRING --rustTarget="${arch}" DO +RUST_TO_GO_ARCH_STRING --rustTarget="${arch}"
@ -197,6 +264,7 @@ go-lint:
# Builds portmaster-start, portmaster-core, hub and notifier for all supported platforms # Builds portmaster-start, portmaster-core, hub and notifier for all supported platforms
go-release: go-release:
FROM ${work_image} FROM ${work_image}
ARG --required architectures
FOR arch IN ${architectures} FOR arch IN ${architectures}
DO +RUST_TO_GO_ARCH_STRING --rustTarget="${arch}" DO +RUST_TO_GO_ARCH_STRING --rustTarget="${arch}"
@ -218,11 +286,6 @@ go-build-utils:
BUILD +go-build --CMDS="" --GOARCH=amd64 --GOOS=linux BUILD +go-build --CMDS="" --GOARCH=amd64 --GOOS=linux
BUILD +go-build --CMDS="" --GOARCH=amd64 --GOOS=windows BUILD +go-build --CMDS="" --GOARCH=amd64 --GOOS=windows
# All targets that should run in CI for go.
go-ci:
BUILD +go-release
BUILD +go-test
# Prepares the angular project by installing dependencies # Prepares the angular project by installing dependencies
angular-deps: angular-deps:
FROM ${node_builder_image} FROM ${node_builder_image}
@ -302,9 +365,6 @@ assets:
rust-base: rust-base:
FROM ${rust_builder_image} FROM ${rust_builder_image}
RUN dpkg --add-architecture armhf
RUN dpkg --add-architecture arm64
RUN apt-get update -qq RUN apt-get update -qq
# Tools and libraries required for cross-compilation # Tools and libraries required for cross-compilation
@ -318,74 +378,39 @@ rust-base:
gcc-multilib \ gcc-multilib \
linux-libc-dev \ linux-libc-dev \
linux-libc-dev-amd64-cross \ linux-libc-dev-amd64-cross \
linux-libc-dev-arm64-cross \
linux-libc-dev-armel-cross \
linux-libc-dev-armhf-cross \
build-essential \ build-essential \
curl \ curl \
wget \ wget \
file \ file \
libsoup-3.0-dev \ libsoup-3.0-dev \
libwebkit2gtk-4.1-dev libwebkit2gtk-4.1-dev \
gcc-mingw-w64-x86-64 \
zip
# Install library dependencies for all supported architectures # Install library dependencies for all supported architectures
# required for succesfully linking. # required for succesfully linking.
FOR arch IN amd64 arm64 armhf
RUN apt-get install --no-install-recommends -qq \ RUN apt-get install --no-install-recommends -qq \
libsoup-3.0-0:${arch} \ libsoup-3.0-0 \
libwebkit2gtk-4.1-0:${arch} \ libwebkit2gtk-4.1-0 \
libssl3:${arch} \ libssl3 \
libayatana-appindicator3-1:${arch} \ libayatana-appindicator3-1 \
librsvg2-bin:${arch} \ librsvg2-bin \
libgtk-3-0:${arch} \ libgtk-3-0 \
libjavascriptcoregtk-4.1-0:${arch} \ libjavascriptcoregtk-4.1-0 \
libssl-dev:${arch} \ libssl-dev \
libayatana-appindicator3-dev:${arch} \ libayatana-appindicator3-dev \
librsvg2-dev:${arch} \ librsvg2-dev \
libgtk-3-dev:${arch} \ libgtk-3-dev \
libjavascriptcoregtk-4.1-dev:${arch} libjavascriptcoregtk-4.1-dev
END
# Note(ppacher): I've no idea why we need to explicitly create those symlinks:
# Some how all the other libs work but libsoup and libwebkit2gtk do not create the link file
RUN cd /usr/lib/aarch64-linux-gnu && \
ln -s libwebkit2gtk-4.1.so.0 libwebkit2gtk-4.1.so && \
ln -s libsoup-3.0.so.0 libsoup-3.0.so
RUN cd /usr/lib/arm-linux-gnueabihf && \
ln -s libwebkit2gtk-4.1.so.0 libwebkit2gtk-4.1.so && \
ln -s libsoup-3.0.so.0 libsoup-3.0.so
# For what ever reason trying to install the gcc compilers together with the above
# command makes apt fail due to conflicts with gcc-multilib. Installing in a separate
# step seems to work ...
RUN apt-get install --no-install-recommends -qq \
g++-mingw-w64-x86-64 \
gcc-aarch64-linux-gnu \
gcc-arm-none-eabi \
gcc-arm-linux-gnueabi \
gcc-arm-linux-gnueabihf \
libc6-dev-arm64-cross \
libc6-dev-armel-cross \
libc6-dev-armhf-cross \
libc6-dev-amd64-cross
# Add some required rustup components # Add some required rustup components
RUN rustup component add clippy RUN rustup component add clippy
RUN rustup component add rustfmt RUN rustup component add rustfmt
# Install architecture targets
FOR arch IN ${architectures}
RUN rustup target add ${arch}
END
DO rust+INIT --keep_fingerprints=true DO rust+INIT --keep_fingerprints=true
# For now we need tauri-cli 1.5 for bulding # For now we need tauri-cli 2.0.0 for bulding
DO rust+CARGO --args="install tauri-cli --version ^1.5.11" DO rust+CARGO --args="install tauri-cli --version ^2.0.0-beta"
# Required for cross compilation to work.
ENV PKG_CONFIG_ALLOW_CROSS=1
# Explicitly cache here. # Explicitly cache here.
SAVE IMAGE --cache-hint SAVE IMAGE --cache-hint
@ -398,7 +423,7 @@ tauri-src:
# --keep-ts is necessary to ensure that the timestamps of the source files # --keep-ts is necessary to ensure that the timestamps of the source files
# are preserved such that Rust's incremental compilation works correctly. # are preserved such that Rust's incremental compilation works correctly.
COPY --keep-ts ./desktop/tauri/ . COPY --keep-ts ./desktop/tauri/ .
COPY assets/data ./assets COPY assets/data ./../../assets/data
COPY packaging/linux ./../../packaging/linux COPY packaging/linux ./../../packaging/linux
COPY (+angular-project/output/tauri-builtin --project=tauri-builtin --dist=./dist/tauri-builtin --configuration=production --baseHref="/") ./../angular/dist/tauri-builtin COPY (+angular-project/output/tauri-builtin --project=tauri-builtin --dist=./dist/tauri-builtin --configuration=production --baseHref="/") ./../angular/dist/tauri-builtin
@ -411,13 +436,151 @@ tauri-build:
FROM +tauri-src FROM +tauri-src
ARG --required target ARG --required target
ARG output=".*/release/(([^\./]+|([^\./]+\.(dll|exe)))|bundle/.*\.(deb|msi|AppImage))" ARG output=".*/release/(([^\./]+|([^\./]+\.(dll|exe)))|bundle/(deb|rpm)/.*\.(deb|rpm))"
ARG bundle="none" ARG bundle="none"
# if we want tauri to create the installer bundles we also need to provide all external binaries # if we want tauri to create the installer bundles we also need to provide all external binaries
# we need to do some magic here because tauri expects the binaries to include the rust target tripple. # we need to do some magic here because tauri expects the binaries to include the rust target tripple.
# We already knwo that triple because it's a required argument. From that triple, we use +RUST_TO_GO_ARCH_STRING # We already know that triple because it's a required argument. From that triple, we use +RUST_TO_GO_ARCH_STRING
# function from below to parse the triple and guess wich GOOS and GOARCH we need.
RUN mkdir /tmp/gobuild
RUN mkdir ./binaries
DO +RUST_TO_GO_ARCH_STRING --rustTarget="${target}"
RUN echo "GOOS=${GOOS} GOARCH=${GOARCH} GOARM=${GOARM} GO_ARCH_STRING=${GO_ARCH_STRING}"
# Our tauri app has externalBins configured so tauri will try to embed them when it finished compiling
# the app. Make sure we copy portmaster-start and portmaster-core in all architectures supported.
# See documentation for externalBins for more information on how tauri searches for the binaries.
COPY (+go-build/output --CMDS="portmaster-start portmaster-core" --GOOS="${GOOS}" --GOARCH="${GOARCH}" --GOARM="${GOARM}") /tmp/gobuild
# Place them in the correct folder with the rust target tripple attached.
FOR bin IN $(ls /tmp/gobuild)
# ${bin$.*} does not work in SET commands unfortunately so we use a shell
# snippet here:
RUN set -e ; \
dest="./binaries/${bin}-${target}" ; \
if [ -z "${bin##*.exe}" ]; then \
dest="./binaries/${bin%.*}-${target}.exe" ; \
fi ; \
cp "/tmp/gobuild/${bin}" "${dest}" ;
END
# Just for debugging ...
# RUN ls -R ./binaries
# The following is exected to work but doesn't. for whatever reason cargo-sweep errors out on the windows-toolchain.
#
# DO rust+CARGO --args="tauri build --bundles none --ci --target=${target}" --output="release/[^/\.]+"
#
# For, now, we just directly mount the rust target cache and call cargo ourself.
DO rust+SET_CACHE_MOUNTS_ENV
RUN rustup target add "${target}"
RUN --mount=$EARTHLY_RUST_TARGET_CACHE cargo tauri build --ci --target="${target}"
DO rust+COPY_OUTPUT --output="${output}"
# BUG(cross-compilation):
#
# The above command seems to correctly compile for all architectures we want to support but fails during
# linking since the target libaries are not available for the requested platforms. Maybe we need to download
# the, manually ...
#
# The earthly rust lib also has support for using cross-rs for cross-compilation but that fails due to the
# fact that cross-rs base docker images used for building are heavily outdated (latest = ubunut:16.0, main = ubuntu:20.04)
# which does not ship recent enough glib versions (our glib dependency needs glib>2.70 but ubunut:20.04 only ships 2.64)
#
# The following would use the CROSS function from the earthly lib, this
# DO rust+CROSS --target="${target}"
RUN echo output: $(ls -R "target/${target}/release")
LET outbin="error"
FOR bin IN "portmaster Portmaster.exe WebView2Loader.dll"
# Modify output binary.
SET outbin="${bin}"
IF [ "${bin}" = "portmaster" ]
SET outbin="portmaster-app"
ELSE IF [ "${bin}" = "Portmaster.exe" ]
SET outbin="portmaster-app.exe"
END
# Save output binary as local artifact.
SAVE ARTIFACT --if-exists --keep-ts "target/${target}/release/${bin}" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/${outbin}"
END
SAVE ARTIFACT --if-exists --keep-ts "target/${target}/release/bundle/deb/*.deb" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/"
SAVE ARTIFACT --if-exists --keep-ts "target/${target}/release/bundle/rpm/*.rpm" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/"
tauri-windows-release-bundle:
FROM +tauri-src
ARG target="x86_64-pc-windows-gnu"
ARG output=".*/release/(([^\./]+|([^\./]+\.(dll|exe))))"
ARG bundle="none"
ARG GOOS=windows
ARG GOARCH=amd64
ARG GOARM
# The binaries will not be used but we still need to create them. Tauri will check for them.
RUN mkdir /tmp/gobuild
RUN mkdir ./binaries
DO +RUST_TO_GO_ARCH_STRING --rustTarget="${target}"
RUN echo "GOOS=${GOOS} GOARCH=${GOARCH} GOARM=${GOARM} GO_ARCH_STRING=${GO_ARCH_STRING}"
# Our tauri app has externalBins configured so tauri will look for them when it finished compiling
# the app. Make sure we copy portmaster-start and portmaster-core in all architectures supported.
# See documentation for externalBins for more information on how tauri searches for the binaries.
COPY (+go-build/output --GOOS="${GOOS}" --CMDS="portmaster-start portmaster-core" --GOARCH="${GOARCH}" --GOARM="${GOARM}") /tmp/gobuild
# Place them in the correct folder with the rust target tripple attached.
FOR bin IN $(ls /tmp/gobuild)
# ${bin$.*} does not work in SET commands unfortunately so we use a shell
# snippet here:
RUN set -e ; \
dest="./binaries/${bin}-${target}" ; \
if [ -z "${bin##*.exe}" ]; then \
dest="./binaries/${bin%.*}-${target}.exe" ; \
fi ; \
cp "/tmp/gobuild/${bin}" "${dest}" ;
END
# Just for debugging ...
# RUN ls -R ./binaries
DO rust+SET_CACHE_MOUNTS_ENV
RUN rustup target add "${target}"
RUN --mount=$EARTHLY_RUST_TARGET_CACHE cargo tauri build --no-bundle --ci --target="${target}"
DO rust+COPY_OUTPUT --output="${output}"
# Get version from git.
COPY .git .
LET version = "$(git tag --points-at || true)"
IF [ -z "${version}" ]
LET dev_version = "$(git describe --tags --first-parent --abbrev=0 || true)"
IF [ -n "${dev_version}" ]
SET version = "${dev_version}"
END
END
IF [ -z "${version}" ]
SET version = "v0.0.0"
END
ENV VERSION="${version}"
RUN echo "Version: $VERSION"
ENV VERSION_SUFFIX="$(echo $VERSION | tr '.' '-')"
RUN echo "Version Suffix: $VERSION_SUFFIX"
RUN echo output: $(ls -R "target/${target}/release")
RUN mv "target/${target}/release/app.exe" "target/${target}/release/portmaster-app_${VERSION_SUFFIX}.exe"
RUN zip "target/${target}/release/portmaster-app_${VERSION_SUFFIX}.zip" "target/${target}/release/portmaster-app_${VERSION_SUFFIX}.exe" -j portmaster-app${VERSION_SUFFIX}.exe "target/${target}/release/WebView2Loader.dll" -j WebView2Loader.dll
SAVE ARTIFACT --if-exists "target/${target}/release/portmaster-app_${VERSION_SUFFIX}.zip" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/"
tauri-prep-windows:
FROM +angular-base --configuration=production
ARG target="x86_64-pc-windows-msvc"
# if we want tauri to create the installer bundles we also need to provide all external binaries
# we need to do some magic here because tauri expects the binaries to include the rust target tripple.
# We already know that triple because it's a required argument. From that triple, we use +RUST_TO_GO_ARCH_STRING
# function from below to parse the triple and guess wich GOOS and GOARCH we need. # function from below to parse the triple and guess wich GOOS and GOARCH we need.
RUN mkdir /tmp/gobuild RUN mkdir /tmp/gobuild
RUN mkdir ./binaries RUN mkdir ./binaries
@ -443,55 +606,29 @@ tauri-build:
cp "/tmp/gobuild/${bin}" "${dest}" ; cp "/tmp/gobuild/${bin}" "${dest}" ;
END END
# Copy source
COPY --keep-ts ./desktop/tauri/src-tauri src-tauri
COPY --keep-ts ./assets assets
# Build UI
ENV NODE_ENV="production"
RUN --no-cache ./node_modules/.bin/ng build --configuration production --base-href / "tauri-builtin"
# Just for debugging ... # Just for debugging ...
RUN ls -R ./binaries # RUN ls -R ./binaries
# RUN ls -R ./dist
# The following is exected to work but doesn't. for whatever reason cargo-sweep errors out on the windows-toolchain. SAVE ARTIFACT "./dist/tauri-builtin" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/desktop/angular/dist/"
# SAVE ARTIFACT "./src-tauri" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/desktop/tauri/src-tauri"
# DO rust+CARGO --args="tauri build --bundles none --ci --target=${target}" --output="release/[^/\.]+" SAVE ARTIFACT "./binaries" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/desktop/tauri/src-tauri/"
# SAVE ARTIFACT "./assets" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/assets"
# For, now, we just directly mount the rust target cache and call cargo ourself.
DO rust+SET_CACHE_MOUNTS_ENV
RUN --mount=$EARTHLY_RUST_TARGET_CACHE cargo tauri build --bundles "${bundle}" --ci --target="${target}"
DO rust+COPY_OUTPUT --output="${output}"
# BUG(cross-compilation):
#
# The above command seems to correctly compile for all architectures we want to support but fails during
# linking since the target libaries are not available for the requested platforms. Maybe we need to download
# the, manually ...
#
# The earthly rust lib also has support for using cross-rs for cross-compilation but that fails due to the
# fact that cross-rs base docker images used for building are heavily outdated (latest = ubunut:16.0, main = ubuntu:20.04)
# which does not ship recent enough glib versions (our glib dependency needs glib>2.70 but ubunut:20.04 only ships 2.64)
#
# The following would use the CROSS function from the earthly lib, this
# DO rust+CROSS --target="${target}"
# RUN echo output: $(ls "target/${target}/release")
LET outbin="error"
FOR bin IN "portmaster Portmaster.exe WebView2Loader.dll"
# Modify output binary.
SET outbin="${bin}"
IF [ "${bin}" = "portmaster" ]
SET outbin="portmaster-app"
ELSE IF [ "${bin}" = "Portmaster.exe" ]
SET outbin="portmaster-app.exe"
END
# Save output binary as local artifact.
IF [ -f "target/${target}/release/${bin}" ]
SAVE ARTIFACT --keep-ts "target/${target}/release/${bin}" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/${outbin}"
END
END
tauri-release: tauri-release:
FROM ${work_image} FROM ${work_image}
ARG --required architectures
ARG bundle="none"
FOR arch IN ${architectures} FOR arch IN ${architectures}
BUILD +tauri-build --target="${arch}" --bundle="${bundle}" BUILD +tauri-build --target="${arch}"
END END
kext-build: kext-build:
@ -515,21 +652,6 @@ kext-build:
SAVE ARTIFACT --keep-ts "portmaster-kext-release-bundle.zip" AS LOCAL "${outputDir}/windows_amd64/portmaster-kext-release-bundle.zip" SAVE ARTIFACT --keep-ts "portmaster-kext-release-bundle.zip" AS LOCAL "${outputDir}/windows_amd64/portmaster-kext-release-bundle.zip"
build:
BUILD +go-release
BUILD +angular-release
BUILD +tauri-release
BUILD +assets
release:
LOCALLY
IF ! git diff --quiet
RUN echo -e "\033[1;31m Refusing to release a dirty git repository. Please commit your local changes first! \033[0m" ; exit 1
END
BUILD +build
# Takes GOOS, GOARCH and optionally GOARM and creates a string representation for file-names. # Takes GOOS, GOARCH and optionally GOARM and creates a string representation for file-names.
# in the form of ${GOOS}_{GOARCH} if GOARM is empty, otherwise ${GOOS}_${GOARCH}v${GOARM}. # in the form of ${GOOS}_{GOARCH} if GOARM is empty, otherwise ${GOOS}_${GOARCH}v${GOARM}.

View file

@ -0,0 +1,14 @@
#!/bin/sh
# Traymenu icons. Sometimes to wrong size is selected so remove 256 and 64.
convert pm_dark_green_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_dark_green.ico
convert pm_dark_blue_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_dark_blue.ico
convert pm_dark_red_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_dark_red.ico
convert pm_dark_yellow_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_dark_yellow.ico
convert pm_light_blue_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_light_blue.ico
convert pm_light_green_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_light_green.ico
convert pm_light_red_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_light_red.ico
convert pm_light_yellow_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_light_yellow.ico
convert pm_dark_512.png -colors 256 -define icon:auto-resize=64,48,32,16 pm_dark.ico
convert pm_light_512.png -colors 256 -define icon:auto-resize=64,48,32,16 pm_light.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

View file

@ -14,16 +14,16 @@ const (
// Icons. // Icons.
var ( var (
//go:embed data/icons/pm_light_green_512.ico //go:embed data/icons/pm_light_green_256.png
GreenICO []byte GreenICO []byte
//go:embed data/icons/pm_light_yellow_512.ico //go:embed data/icons/pm_light_yellow_256.png
YellowICO []byte YellowICO []byte
//go:embed data/icons/pm_light_red_512.ico //go:embed data/icons/pm_light_red_256.png
RedICO []byte RedICO []byte
//go:embed data/icons/pm_light_blue_512.ico //go:embed data/icons/pm_light_blue_256.png
BlueICO []byte BlueICO []byte
// ColoredIcons holds all the icons as .ICOs // ColoredIcons holds all the icons as .ICOs

View file

@ -28,6 +28,7 @@ var (
maxRetries int maxRetries int
dataRoot *utils.DirStructure dataRoot *utils.DirStructure
logsRoot *utils.DirStructure logsRoot *utils.DirStructure
forceOldUI bool
updateURLFlag string updateURLFlag string
userAgentFlag string userAgentFlag string
@ -74,6 +75,7 @@ func init() {
flags.StringVar(&userAgentFlag, "update-agent", "", "Set an alternative user agent for requests to the update server") flags.StringVar(&userAgentFlag, "update-agent", "", "Set an alternative user agent for requests to the update server")
flags.IntVar(&maxRetries, "max-retries", 5, "Maximum number of retries when starting a Portmaster component") flags.IntVar(&maxRetries, "max-retries", 5, "Maximum number of retries when starting a Portmaster component")
flags.BoolVar(&stdinSignals, "input-signals", false, "Emulate signals using stdin.") flags.BoolVar(&stdinSignals, "input-signals", false, "Emulate signals using stdin.")
flags.BoolVar(&forceOldUI, "old-ui", false, "Use the old ui. (Beta)")
_ = rootCmd.MarkPersistentFlagDirname("data") _ = rootCmd.MarkPersistentFlagDirname("data")
_ = flags.MarkHidden("input-signals") _ = flags.MarkHidden("input-signals")
} }

View file

@ -29,6 +29,10 @@ const (
// This disables retrying and exits with an error code. // This disables retrying and exits with an error code.
ControlledFailureExitCode = 24 ControlledFailureExitCode = 24
// StartOldUIExitCode is an exit code that is returned by the UI when there. This is manfully triaged by the user, if the new UI does not work for them.
StartOldUIExitCode = 77
MissingDependencyExitCode = 0xc0000135 // Windows STATUS_DLL_NOT_FOUND
exeSuffix = ".exe" exeSuffix = ".exe"
zipSuffix = ".zip" zipSuffix = ".zip"
) )
@ -38,6 +42,8 @@ var (
onWindows = runtime.GOOS == "windows" onWindows = runtime.GOOS == "windows"
stdinSignals bool stdinSignals bool
childIsRunning = abool.NewBool(false) childIsRunning = abool.NewBool(false)
fallBackToOldUI bool = false
) )
// Options for starting component. // Options for starting component.
@ -55,7 +61,21 @@ type Options struct {
RestartOnFail bool // Try restarting automatically, if the started component fails. RestartOnFail bool // Try restarting automatically, if the started component fails.
} }
// This is a temp value that will be used to test the new UI in beta.
var app2Options = Options{
Name: "Portmaster App2",
Identifier: "app2/portmaster-app",
AllowDownload: false,
AllowHidingWindow: false,
RestartOnFail: true,
}
func init() { func init() {
// Make sure the new UI has a proper extension.
if onWindows {
app2Options.Identifier += ".zip"
}
registerComponent([]Options{ registerComponent([]Options{
{ {
Name: "Portmaster Core", Name: "Portmaster Core",
@ -70,6 +90,7 @@ func init() {
Identifier: "app/portmaster-app.zip", Identifier: "app/portmaster-app.zip",
AllowDownload: false, AllowDownload: false,
AllowHidingWindow: false, AllowHidingWindow: false,
RestartOnFail: true,
}, },
{ {
Name: "Portmaster Notifier", Name: "Portmaster Notifier",
@ -89,6 +110,26 @@ func init() {
RestartOnFail: true, RestartOnFail: true,
}, },
}) })
if onWindows {
registerComponent([]Options{
{
Name: "Portmaster App2",
Identifier: "app2/portmaster-app.zip",
AllowDownload: false,
AllowHidingWindow: false,
},
})
} else {
registerComponent([]Options{
{
Name: "Portmaster App2",
Identifier: "app2/portmaster-app",
AllowDownload: false,
AllowHidingWindow: false,
},
})
}
} }
func registerComponent(opts []Options) { func registerComponent(opts []Options) {
@ -311,6 +352,14 @@ func persistOutputStreams(opts *Options, version string, cmd *exec.Cmd) (chan st
} }
func execute(opts *Options, args []string) (cont bool, err error) { func execute(opts *Options, args []string) (cont bool, err error) {
if !forceOldUI && registry.UsePreReleases && opts.ShortIdentifier == "app" {
// Check if new ui was already tried.
if !fallBackToOldUI {
opts = &app2Options
log.Println("Using new UI")
}
}
file, err := registry.GetFile( file, err := registry.GetFile(
helper.PlatformIdentifier(opts.Identifier), helper.PlatformIdentifier(opts.Identifier),
) )
@ -440,6 +489,12 @@ func parseExitError(err error) (restart bool, errWithCtx error) {
return true, nil return true, nil
case ControlledFailureExitCode: case ControlledFailureExitCode:
return false, errors.New("controlled failure, check logs") return false, errors.New("controlled failure, check logs")
case StartOldUIExitCode:
fallBackToOldUI = true
return true, errors.New("user requested old UI")
case MissingDependencyExitCode:
fallBackToOldUI = true
return true, errors.New("new UI failed with missing dependency")
default: default:
return true, fmt.Errorf("unknown exit code %w", exErr) return true, fmt.Errorf("unknown exit code %w", exErr)
} }

File diff suppressed because it is too large Load diff

View file

@ -37,13 +37,13 @@
"@fortawesome/free-brands-svg-icons": "^6.4.0", "@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0", "@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0",
"@tauri-apps/api": "^2.0.0-beta.3", "@tauri-apps/api": ">=2.0.0-beta.0",
"@tauri-apps/plugin-cli": "^2.0.0-beta.1", "@tauri-apps/plugin-cli": ">=2.0.0-beta.0",
"@tauri-apps/plugin-clipboard-manager": "^2.0.0-alpha.4", "@tauri-apps/plugin-clipboard-manager": ">=2.0.0-beta.0",
"@tauri-apps/plugin-dialog": "^2.0.0-alpha.4", "@tauri-apps/plugin-dialog": ">=2.0.0-beta.0",
"@tauri-apps/plugin-notification": "^2.0.0-alpha.4", "@tauri-apps/plugin-notification": ">=2.0.0-beta.0",
"@tauri-apps/plugin-os": "^2.0.0-alpha.5", "@tauri-apps/plugin-os": ">=2.0.0-beta.0",
"@tauri-apps/plugin-shell": "^2.0.0-alpha.4", "@tauri-apps/plugin-shell": "^2.0.0-beta",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"d3": "^7.8.4", "d3": "^7.8.4",
"data-urls": "^5.0.0", "data-urls": "^5.0.0",

View file

@ -28,7 +28,7 @@ function asyncInvoke<T>(method: string, args: object): Promise<T> {
if (typeof event.payload === 'object' && 'error' in event.payload) { if (typeof event.payload === 'object' && 'error' in event.payload) {
reject(event.payload); reject(event.payload);
return return
}; }
resolve(event.payload); resolve(event.payload);
}) })
@ -75,7 +75,7 @@ export class TauriIntegrationService implements IntegrationService {
} }
getAppInfo(info: ProcessInfo): Promise<AppInfo> { getAppInfo(info: ProcessInfo): Promise<AppInfo> {
return asyncInvoke("plugin:portmaster|get_app_info", { return asyncInvoke("get_app_info", {
...info, ...info,
}) })
} }
@ -112,7 +112,7 @@ export class TauriIntegrationService implements IntegrationService {
async shouldShow(): Promise<boolean> { async shouldShow(): Promise<boolean> {
try { try {
const response = await invoke<string>("plugin:portmaster|should_show"); const response = await invoke<string>("should_show");
return response === "show"; return response === "show";
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@ -122,7 +122,7 @@ export class TauriIntegrationService implements IntegrationService {
async shouldHandlePrompts(): Promise<boolean> { async shouldHandlePrompts(): Promise<boolean> {
try { try {
const response = await invoke<string>("plugin:portmaster|should_handle_prompts") const response = await invoke<string>("should_handle_prompts")
return response === "true" return response === "true"
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@ -130,27 +130,27 @@ export class TauriIntegrationService implements IntegrationService {
} }
} }
get_state(key: string): Promise<string> { get_state(_: string): Promise<string> {
return invoke<string>("plugin:portmaster|get_state"); return invoke<string>("get_state");
} }
set_state(key: string, value: string): Promise<void> { set_state(key: string, value: string): Promise<void> {
return invoke<void>("plugin:portmaster|set_state", { return invoke<void>("set_state", {
key, key,
value value
}) })
} }
getServiceManagerStatus(): Promise<ServiceManagerStatus> { getServiceManagerStatus(): Promise<ServiceManagerStatus> {
return asyncInvoke("plugin:portmaster|get_service_manager_status", {}) return asyncInvoke("get_service_manager_status", {})
} }
startService(): Promise<any> { startService(): Promise<any> {
return asyncInvoke("plugin:portmaster|start_service", {}); return asyncInvoke("start_service", {});
} }
onExitRequest(cb: () => void): () => void { onExitRequest(cb: () => void): () => void {
let unlisten: () => void = () => { }; let unlisten: () => void = () => undefined;
listen('exit-requested', () => { listen('exit-requested', () => {
cb(); cb();
@ -189,7 +189,7 @@ export class TauriIntegrationService implements IntegrationService {
return; return;
} }
let promptWindow = new Window("prompt", { const promptWindow = new Window("prompt", {
alwaysOnTop: true, alwaysOnTop: true,
decorations: false, decorations: false,
minimizable: false, minimizable: false,

View file

@ -1 +0,0 @@
../../assets/data

File diff suppressed because it is too large Load diff

View file

@ -12,21 +12,21 @@ rust-version = "1.60"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies] [build-dependencies]
tauri-build = { version = "2.0.0-alpha", features = [] } tauri-build = { version = "2.0.0-beta.18", features = [] }
[dependencies] [dependencies]
# Tauri # Tauri
tauri = { version = "2.0.0-alpha", features = ["tray-icon", "icon-ico", "icon-png"] } tauri = { version = "2.0.0-beta.23", features = ["tray-icon", "image-png", "config-json5"] }
tauri-plugin-shell = "2.0.0-alpha" tauri-plugin-shell = "2.0.0-beta"
tauri-plugin-dialog = "2.0.0-alpha" tauri-plugin-dialog = "2.0.0-beta"
tauri-plugin-clipboard-manager = "2.0.0-alpha" tauri-plugin-clipboard-manager = "2.0.0-beta"
tauri-plugin-os = "2.0.0-alpha" tauri-plugin-os = "2.0.0-beta"
tauri-plugin-single-instance = "2.0.0-alpha" tauri-plugin-single-instance = "2.0.0-beta"
tauri-plugin-cli = "2.0.0-alpha" tauri-plugin-cli = "2.0.0-beta"
tauri-plugin-notification = "2.0.0-alpha" tauri-plugin-notification = "2.0.0-beta"
tauri-plugin-log = "2.0.0-beta"
# We still need the tauri-cli 1.5 for building tauri-cli = "2.0.0-beta.21"
tauri-cli = "1.5.11"
# General # General
serde_json = "1.0" serde_json = "1.0"
@ -47,7 +47,10 @@ http = "1.0.0"
url = "2.5.0" url = "2.5.0"
thiserror = "1.0" thiserror = "1.0"
log = "0.4.21" log = "0.4.21"
pretty_env_logger = "0.5.0" reqwest = { version = "0.12" }
rfd = { version = "*", default-features = false, features = [ "tokio", "gtk3", "common-controls-v6" ] }
open = "5.1.3"
# Linux only # Linux only
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
@ -62,6 +65,7 @@ gio-sys = "0.18.1"
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
windows-service = "0.6.0" windows-service = "0.6.0"
windows = { version = "0.54.0", features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } windows = { version = "0.54.0", features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
tauri-winrt-notification = "0.3.0"
[dev-dependencies] [dev-dependencies]
which = "6.0.0" which = "6.0.0"

View file

@ -0,0 +1,32 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": [
"main",
"splash"
],
"remote": {
"urls": [
"http://localhost:817"
]
},
"permissions": [
"path:default",
"event:allow-listen",
"event:allow-unlisten",
"event:allow-emit",
"event:allow-emit-to",
"window:allow-hide",
"window:allow-show",
"window:allow-is-visible",
"window:allow-set-focus",
"app:default",
"image:default",
"resources:default",
"menu:default",
"tray:default",
"shell:allow-open",
"notification:default"
]
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"default":{"identifier":"default","description":"Capability for the main window","remote":{"urls":["http://localhost:817"]},"local":true,"windows":["main","splash"],"permissions":["path:default","event:allow-listen","event:allow-unlisten","event:allow-emit","event:allow-emit-to","window:allow-hide","window:allow-show","window:allow-is-visible","window:allow-set-focus","app:default","image:default","resources:default","menu:default","tray:default","shell:allow-open","notification:default"]}}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,8 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!! // Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::time::Duration;
use tauri::{AppHandle, Manager, RunEvent, WindowEvent}; use tauri::{AppHandle, Manager, RunEvent, WindowEvent};
use tauri_plugin_cli::CliExt; use tauri_plugin_cli::CliExt;
@ -16,7 +18,7 @@ mod portmaster;
mod traymenu; mod traymenu;
mod window; mod window;
use log::{debug, error, info, trace, warn}; use log::{debug, error, info};
use portmaster::PortmasterExt; use portmaster::PortmasterExt;
use traymenu::setup_tray_menu; use traymenu::setup_tray_menu;
use window::{close_splash_window, create_main_window}; use window::{close_splash_window, create_main_window};
@ -24,6 +26,8 @@ use window::{close_splash_window, create_main_window};
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
const FALLBACK_TO_OLD_UI_EXIT_CODE: i32 = 77;
#[derive(Clone, serde::Serialize)] #[derive(Clone, serde::Serialize)]
struct Payload { struct Payload {
args: Vec<String>, args: Vec<String>,
@ -82,12 +86,38 @@ impl portmaster::Handler for WsHandler {
} }
} }
fn show_webview_not_installed_dialog() -> i32 {
use rfd::MessageDialog;
let result = MessageDialog::new()
.set_title("Portmaster")
.set_description("Webkit is not installed. Please install it and run portmaster again")
.set_buttons(rfd::MessageButtons::OkCancelCustom(
"Go to install page".to_owned(),
"Use old UI".to_owned(),
))
.show();
println!("{:?}", result);
if let rfd::MessageDialogResult::Custom(result) = result {
if result.eq("Go to install page") {
_ = open::that("https://wiki.safing.io/en/Portmaster/Install/Webview");
std::thread::sleep(Duration::from_secs(2));
return 0;
}
}
return FALLBACK_TO_OLD_UI_EXIT_CODE;
}
fn main() { fn main() {
pretty_env_logger::init(); if let Err(_) = tauri::webview_version() {
std::process::exit(show_webview_not_installed_dialog());
}
let app = tauri::Builder::default() let app = tauri::Builder::default()
// Shell plugin for open_external support // Shell plugin for open_external support
.plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_log::Builder::default().build())
// Clipboard support // Clipboard support
.plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_clipboard_manager::init())
// Dialog (Save/Open) support // Dialog (Save/Open) support
@ -102,16 +132,24 @@ fn main() {
.plugin(tauri_plugin_cli::init()) .plugin(tauri_plugin_cli::init())
// Notification support // Notification support
.plugin(tauri_plugin_notification::init()) .plugin(tauri_plugin_notification::init())
// Our Portmaster Plugin that handles communication between tauri and our angular app. .invoke_handler(tauri::generate_handler![
.plugin(portmaster::init()) portmaster::commands::get_app_info,
portmaster::commands::get_service_manager_status,
portmaster::commands::start_service,
portmaster::commands::get_state,
portmaster::commands::set_state,
portmaster::commands::should_show,
portmaster::commands::should_handle_prompts
])
// Setup the app an any listeners // Setup the app an any listeners
.setup(|app| { .setup(|app| {
setup_tray_menu(app)?; setup_tray_menu(app)?;
portmaster::setup(app.handle().clone());
// Setup the single-instance event listener that will create/focus the main window // Setup the single-instance event listener that will create/focus the main window
// or the splash-screen. // or the splash-screen.
let handle = app.handle().clone(); let handle = app.handle().clone();
app.listen_global("single-instance", move |_event| { app.listen_any("single-instance", move |_event| {
let _ = window::open_window(&handle); let _ = window::open_window(&handle);
}); });
@ -196,17 +234,23 @@ fn main() {
); );
api.prevent_close(); api.prevent_close();
if let Some(window) = handle.get_window(label.as_str()) { if let Some(window) = handle.get_webview_window(label.as_str()) {
let _ = window.emit("exit-requested", ""); let result = window.emit("exit-requested", "");
if let Err(err) = result {
error!("failed to emit event: {}", err.to_string());
}
} else {
error!("window was None");
} }
} }
_ => {} _ => {}
} }
} }
RunEvent::ExitRequested { api, .. } => { // TODO(vladimir): why was this needed?
api.prevent_exit(); // RunEvent::ExitRequested { api, .. } => {
} // api.prevent_exit();
// }
_ => {} _ => {}
}); });
} }

View file

@ -1,4 +1,4 @@
use super::PortmasterPlugin; use super::PortmasterInterface;
use crate::service::get_service_manager; use crate::service::get_service_manager;
use crate::service::ServiceManager; use crate::service::ServiceManager;
use log::debug; use log::debug;
@ -15,7 +15,7 @@ pub struct Error {
#[tauri::command] #[tauri::command]
pub fn should_show<R: Runtime>( pub fn should_show<R: Runtime>(
_window: Window<R>, _window: Window<R>,
portmaster: State<'_, PortmasterPlugin<R>>, portmaster: State<'_, PortmasterInterface<R>>,
) -> Result { ) -> Result {
if portmaster.get_show_after_bootstrap() { if portmaster.get_show_after_bootstrap() {
debug!("[tauri:rpc:should_show] application should show after bootstrap"); debug!("[tauri:rpc:should_show] application should show after bootstrap");
@ -31,7 +31,7 @@ pub fn should_show<R: Runtime>(
#[tauri::command] #[tauri::command]
pub fn should_handle_prompts<R: Runtime>( pub fn should_handle_prompts<R: Runtime>(
_window: Window<R>, _window: Window<R>,
portmaster: State<'_, PortmasterPlugin<R>>, portmaster: State<'_, PortmasterInterface<R>>,
) -> Result { ) -> Result {
if portmaster.handle_prompts.load(Ordering::Relaxed) { if portmaster.handle_prompts.load(Ordering::Relaxed) {
Ok("true".to_string()) Ok("true".to_string())
@ -43,7 +43,7 @@ pub fn should_handle_prompts<R: Runtime>(
#[tauri::command] #[tauri::command]
pub fn get_state<R: Runtime>( pub fn get_state<R: Runtime>(
_window: Window<R>, _window: Window<R>,
portmaster: State<'_, PortmasterPlugin<R>>, portmaster: State<'_, PortmasterInterface<R>>,
key: String, key: String,
) -> Result { ) -> Result {
let value = portmaster.get_state(key); let value = portmaster.get_state(key);
@ -58,7 +58,7 @@ pub fn get_state<R: Runtime>(
#[tauri::command] #[tauri::command]
pub fn set_state<R: Runtime>( pub fn set_state<R: Runtime>(
_window: Window<R>, _window: Window<R>,
portmaster: State<'_, PortmasterPlugin<R>>, portmaster: State<'_, PortmasterInterface<R>>,
key: String, key: String,
value: String, value: String,
) -> Result { ) -> Result {

View file

@ -13,7 +13,7 @@
/// in the crate root. /// in the crate root.
// The commands module contains tauri commands that are available to Javascript // The commands module contains tauri commands that are available to Javascript
// using the invoke() and our custom invokeAsync() command. // using the invoke() and our custom invokeAsync() command.
mod commands; pub mod commands;
// The websocket module spawns an async function on tauri's runtime that manages // The websocket module spawns an async function on tauri's runtime that manages
// a persistent connection to the Portmaster websocket API and updates the tauri Portmaster // a persistent connection to the Portmaster websocket API and updates the tauri Portmaster
@ -34,10 +34,9 @@ use std::{
use log::{debug, error}; use log::{debug, error};
use serde; use serde;
use std::sync::Mutex; use std::sync::Mutex;
use tauri::{ use tauri::{AppHandle, Manager, Runtime};
plugin::{Builder, TauriPlugin},
AppHandle, Manager, Runtime, const PORTMASTER_BASE_URL: &'static str = "http://127.0.0.1:817/api/v1/";
};
pub trait Handler { pub trait Handler {
fn on_connect(&mut self, cli: PortAPI) -> (); fn on_connect(&mut self, cli: PortAPI) -> ();
@ -45,7 +44,7 @@ pub trait Handler {
fn name(&self) -> String; fn name(&self) -> String;
} }
pub struct PortmasterPlugin<R: Runtime> { pub struct PortmasterInterface<R: Runtime> {
#[allow(dead_code)] #[allow(dead_code)]
app: AppHandle<R>, app: AppHandle<R>,
@ -76,7 +75,7 @@ pub struct PortmasterPlugin<R: Runtime> {
should_show_after_bootstrap: AtomicBool, should_show_after_bootstrap: AtomicBool,
} }
impl<R: Runtime> PortmasterPlugin<R> { impl<R: Runtime> PortmasterInterface<R> {
/// Returns a state stored in the portmaster plugin. /// Returns a state stored in the portmaster plugin.
pub fn get_state(&self, key: String) -> Option<String> { pub fn get_state(&self, key: String) -> Option<String> {
let map = self.state.lock(); let map = self.state.lock();
@ -124,7 +123,6 @@ impl<R: Runtime> PortmasterPlugin<R> {
} }
handlers.push(Box::new(handler)); handlers.push(Box::new(handler));
debug!("number of registered handlers: {}", handlers.len()); debug!("number of registered handlers: {}", handlers.len());
} }
} }
@ -174,7 +172,7 @@ impl<R: Runtime> PortmasterPlugin<R> {
self.should_show_after_bootstrap.load(Ordering::Relaxed) self.should_show_after_bootstrap.load(Ordering::Relaxed)
} }
/// Tells the angular applicatoin to show the window by emitting an event. /// Tells the angular application to show the window by emitting an event.
/// It calls set_show_after_bootstrap(true) automatically so the application /// It calls set_show_after_bootstrap(true) automatically so the application
/// also shows after bootstrapping. /// also shows after bootstrapping.
pub fn show_window(&self) { pub fn show_window(&self) {
@ -184,8 +182,9 @@ impl<R: Runtime> PortmasterPlugin<R> {
// misses the event below because it's still bootstrapping. // misses the event below because it's still bootstrapping.
self.set_show_after_bootstrap(true); self.set_show_after_bootstrap(true);
// ignore the error here, there's nothing we could do about it anyways. if let Err(err) = self.app.emit("portmaster:show", "") {
let _ = self.app.emit("portmaster:show", ""); error!("failed to emit show event: {}", err.to_string());
}
} }
/// Enables or disables the SPN. /// Enables or disables the SPN.
@ -206,12 +205,30 @@ impl<R: Runtime> PortmasterPlugin<R> {
} }
} }
/// Send Shutdown request to portmaster
pub fn trigger_shutdown(&self) {
tauri::async_runtime::spawn(async move {
let client = reqwest::Client::new();
match client
.post(format!("{}core/shutdown", PORTMASTER_BASE_URL))
.send()
.await
{
Ok(v) => {
debug!("shutdown request sent {:?}", v);
}
Err(err) => {
error!("failed to send shutdown request {}", err);
}
}
});
}
//// Internal functions //// Internal functions
fn start_notification_handler(&self) { fn start_notification_handler(&self) {
if let Some(api) = self.get_api() { if let Some(api) = self.get_api() {
let cli = api.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
notifications::notification_handler(cli).await; notifications::notification_handler(api).await;
}); });
} }
} }
@ -223,9 +240,10 @@ impl<R: Runtime> PortmasterPlugin<R> {
self.is_reachable.store(true, Ordering::Relaxed); self.is_reachable.store(true, Ordering::Relaxed);
// store the new api client. // store the new api client.
{
let mut guard = self.api.lock().unwrap(); let mut guard = self.api.lock().unwrap();
*guard = Some(api.clone()); *guard = Some(api.clone());
drop(guard); }
// fire-off the notification handler. // fire-off the notification handler.
if self.handle_notifications.load(Ordering::Relaxed) { if self.handle_notifications.load(Ordering::Relaxed) {
@ -249,9 +267,10 @@ impl<R: Runtime> PortmasterPlugin<R> {
self.is_reachable.store(false, Ordering::Relaxed); self.is_reachable.store(false, Ordering::Relaxed);
// clear the current api client reference. // clear the current api client reference.
{
let mut guard = self.api.lock().unwrap(); let mut guard = self.api.lock().unwrap();
*guard = None; *guard = None;
drop(guard); }
if let Ok(mut handlers) = self.handlers.lock() { if let Ok(mut handlers) = self.handlers.lock() {
for handler in handlers.iter_mut() { for handler in handlers.iter_mut() {
@ -262,31 +281,20 @@ impl<R: Runtime> PortmasterPlugin<R> {
} }
pub trait PortmasterExt<R: Runtime> { pub trait PortmasterExt<R: Runtime> {
fn portmaster(&self) -> &PortmasterPlugin<R>; fn portmaster(&self) -> &PortmasterInterface<R>;
} }
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct Config {} pub struct Config {}
impl<R: Runtime, T: Manager<R>> PortmasterExt<R> for T { impl<R: Runtime, T: Manager<R>> PortmasterExt<R> for T {
fn portmaster(&self) -> &PortmasterPlugin<R> { fn portmaster(&self) -> &PortmasterInterface<R> {
self.state::<PortmasterPlugin<R>>().inner() self.state::<PortmasterInterface<R>>().inner()
} }
} }
pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> { pub fn setup(app: AppHandle) {
Builder::<R, Option<Config>>::new("portmaster") let interface = PortmasterInterface {
.invoke_handler(tauri::generate_handler![
commands::get_app_info,
commands::get_service_manager_status,
commands::start_service,
commands::get_state,
commands::set_state,
commands::should_show,
commands::should_handle_prompts
])
.setup(|app, _api| {
let plugin = PortmasterPlugin {
app: app.clone(), app: app.clone(),
state: Mutex::new(HashMap::new()), state: Mutex::new(HashMap::new()),
is_reachable: AtomicBool::new(false), is_reachable: AtomicBool::new(false),
@ -297,12 +305,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
should_show_after_bootstrap: AtomicBool::new(true), should_show_after_bootstrap: AtomicBool::new(true),
}; };
app.manage(plugin); app.manage(interface);
// fire of the websocket handler // fire of the websocket handler
websocket::start_websocket_thread(app.clone()); websocket::start_websocket_thread(app.clone());
Ok(())
})
.build()
} }

View file

@ -3,9 +3,7 @@ use crate::portapi::message::*;
use crate::portapi::models::notification::*; use crate::portapi::models::notification::*;
use crate::portapi::types::*; use crate::portapi::types::*;
use log::error; use log::error;
use notify_rust;
use serde_json::json; use serde_json::json;
#[allow(unused_imports)]
use tauri::async_runtime; use tauri::async_runtime;
pub async fn notification_handler(cli: PortAPI) { pub async fn notification_handler(cli: PortAPI) {
@ -34,10 +32,24 @@ pub async fn notification_handler(cli: PortAPI) {
if n.selected_action_id != "" { if n.selected_action_id != "" {
return; return;
} }
show_notification(&cli, key, n).await;
}
Err(err) => match err {
ParseError::JSON(err) => {
error!("failed to parse notification: {}", err);
}
_ => {
error!("unknown error when parsing notifications payload");
}
},
}
}
}
}
}
// TODO(ppacher): keep a reference of open notifications and close them #[cfg(target_os = "linux")]
// if the user reacted inside the UI: pub async fn show_notification(cli: &PortAPI, key: String, n: Notification) {
let mut notif = notify_rust::Notification::new(); let mut notif = notify_rust::Notification::new();
notif.body(&n.message); notif.body(&n.message);
notif.timeout(notify_rust::Timeout::Never); // TODO(ppacher): use n.expires to calculate the timeout. notif.timeout(notify_rust::Timeout::Never); // TODO(ppacher): use n.expires to calculate the timeout.
@ -48,11 +60,12 @@ pub async fn notification_handler(cli: PortAPI) {
notif.action(&action.id, &action.text); notif.action(&action.id, &action.text);
} }
#[cfg(target_os = "linux")]
{ {
let cli_clone = cli.clone(); let cli_clone = cli.clone();
async_runtime::spawn(async move { async_runtime::spawn(async move {
let res = notif.show(); let res = notif.show();
// TODO(ppacher): keep a reference of open notifications and close them
// if the user reacted inside the UI:
match res { match res {
Ok(handle) => { Ok(handle) => {
handle.wait_for_action(|action| { handle.wait_for_action(|action| {
@ -87,17 +100,46 @@ pub async fn notification_handler(cli: PortAPI) {
} }
}); });
} }
} }
Err(err) => match err {
ParseError::JSON(err) => { #[cfg(target_os = "windows")]
error!("failed to parse notification: {}", err); pub async fn show_notification(cli: &PortAPI, key: String, n: Notification) {
} use tauri_winrt_notification::{Duration, Sound, Toast};
_ => {
error!("unknown error when parsing notifications payload"); let mut toast = Toast::new("io.safing.portmaster")
} .title(&n.title)
}, .text1(&n.message)
} .sound(Some(Sound::Default))
} .duration(Duration::Long);
}
} for action in n.actions {
toast = toast.add_button(&action.text, &action.id);
}
{
let cli = cli.clone();
toast = toast.on_activated(move |action| -> windows::core::Result<()> {
if let Some(value) = action {
let cli = cli.clone();
let key = key.clone();
async_runtime::spawn(async move {
let _ = cli
.request(Request::Update(
key,
Payload::JSON(
json!({
"SelectedActionID": value
})
.to_string(),
),
))
.await;
});
}
// TODO(vladimir): If Action is None, the user clicked on the notification. Focus on the UI.
Ok(())
});
}
toast.show().expect("unable to send notification");
// TODO(vladimir): keep a reference of open notifications and close them
// if the user reacted inside the UI:
} }

View file

@ -12,7 +12,6 @@ use std::process::ExitStatus;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use crate::service::systemd::SystemdServiceManager; use crate::service::systemd::SystemdServiceManager;
use log::info;
use thiserror::Error; use thiserror::Error;
use self::status::StatusResult; use self::status::StatusResult;
@ -63,7 +62,7 @@ pub fn get_service_manager() -> Result<impl ServiceManager> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
if SystemdServiceManager::is_installed() { if SystemdServiceManager::is_installed() {
info!("system service manager: systemd"); log::info!("system service manager: systemd");
Ok(SystemdServiceManager {}) Ok(SystemdServiceManager {})
} else { } else {

View file

@ -1,16 +1,15 @@
use std::collections::HashMap; use std::sync::atomic::AtomicBool;
use std::sync::Mutex; use std::sync::Mutex;
use std::{collections::HashMap, sync::atomic::Ordering};
use log::{debug, error}; use log::{debug, error};
use tauri::tray::{MouseButton, MouseButtonState};
use tauri::{ use tauri::{
menu::{ image::Image,
CheckMenuItem, CheckMenuItemBuilder, MenuBuilder, MenuItemBuilder, PredefinedMenuItem, menu::{MenuBuilder, MenuItem, MenuItemBuilder, PredefinedMenuItem, SubmenuBuilder},
SubmenuBuilder, tray::{TrayIcon, TrayIconBuilder},
}, Wry,
tray::{ClickType, TrayIcon, TrayIconBuilder},
Icon, Manager, Wry,
}; };
use tauri_plugin_dialog::DialogExt;
use crate::{ use crate::{
portapi::{ portapi::{
@ -26,62 +25,90 @@ use crate::{
portmaster::PortmasterExt, portmaster::PortmasterExt,
window::{create_main_window, may_navigate_to_ui, open_window}, window::{create_main_window, may_navigate_to_ui, open_window},
}; };
use tauri_plugin_dialog::DialogExt;
pub type AppIcon = TrayIcon<Wry>; pub type AppIcon = TrayIcon<Wry>;
static SPN_STATE: AtomicBool = AtomicBool::new(false);
lazy_static! { lazy_static! {
// Set once setup_tray_menu executed. static ref SPN_STATUS: Mutex<Option<MenuItem<Wry>>> = Mutex::new(None);
static ref SPN_BUTTON: Mutex<Option<CheckMenuItem<Wry>>> = Mutex::new(None); static ref SPN_BUTTON: Mutex<Option<MenuItem<Wry>>> = Mutex::new(None);
static ref GLOBAL_STATUS: Mutex<Option<MenuItem<Wry>>> = Mutex::new(None);
} }
const PM_TRAY_ICON_ID: &'static str = "pm_icon";
// Icons // Icons
// //
const BLUE_ICON: &'static [u8] = const BLUE_ICON: &'static [u8] = include_bytes!("../../../../assets/data/icons/pm_light_blue.ico");
include_bytes!("../../assets/icons/pm_light_blue_512.ico"); const RED_ICON: &'static [u8] = include_bytes!("../../../../assets/data/icons/pm_light_red.ico");
const RED_ICON: &'static [u8] =
include_bytes!("../../assets/icons/pm_light_red_512.ico");
const YELLOW_ICON: &'static [u8] = const YELLOW_ICON: &'static [u8] =
include_bytes!("../../assets/icons/pm_light_yellow_512.ico"); include_bytes!("../../../../assets/data/icons/pm_light_yellow.ico");
const GREEN_ICON: &'static [u8] = const GREEN_ICON: &'static [u8] =
include_bytes!("../../assets/icons/pm_light_green_512.ico"); include_bytes!("../../../../assets/data/icons/pm_light_green.ico");
pub fn setup_tray_menu( pub fn setup_tray_menu(
app: &mut tauri::App, app: &mut tauri::App,
) -> core::result::Result<AppIcon, Box<dyn std::error::Error>> { ) -> core::result::Result<AppIcon, Box<dyn std::error::Error>> {
// Tray menu // Tray menu
let close_btn = MenuItemBuilder::with_id("close", "Exit").build(app); let open_btn = MenuItemBuilder::with_id("open", "Open App").build(app)?;
let open_btn = MenuItemBuilder::with_id("open", "Open").build(app); let exit_ui_btn = MenuItemBuilder::with_id("exit_ui", "Exit UI").build(app)?;
let shutdown_btn = MenuItemBuilder::with_id("shutdown", "Shut Down Portmaster").build(app)?;
let spn = CheckMenuItemBuilder::with_id("spn", "Use SPN").build(app); let global_status = MenuItemBuilder::with_id("global_status", "Status: Secured")
.enabled(false)
.build(app)
.unwrap();
{
let mut button_ref = GLOBAL_STATUS.lock()?;
*button_ref = Some(global_status.clone());
}
// Store the SPN button reference // Setup SPN status
let mut button_ref = SPN_BUTTON.lock().unwrap(); let spn_status = MenuItemBuilder::with_id("spn_status", "SPN: Disabled")
.enabled(false)
.build(app)
.unwrap();
{
let mut button_ref = SPN_STATUS.lock()?;
*button_ref = Some(spn_status.clone());
}
// Setup SPN button
let spn = MenuItemBuilder::with_id("spn_toggle", "Enable SPN")
.build(app)
.unwrap();
{
let mut button_ref = SPN_BUTTON.lock()?;
*button_ref = Some(spn.clone()); *button_ref = Some(spn.clone());
}
let force_show_window = MenuItemBuilder::with_id("force-show", "Force Show UI").build(app); let force_show_window = MenuItemBuilder::with_id("force-show", "Force Show UI").build(app)?;
let reload_btn = MenuItemBuilder::with_id("reload", "Reload User Interface").build(app); let reload_btn = MenuItemBuilder::with_id("reload", "Reload User Interface").build(app)?;
let developer_menu = SubmenuBuilder::new(app, "Developer") let developer_menu = SubmenuBuilder::new(app, "Developer")
.items(&[&reload_btn, &force_show_window]) .items(&[&reload_btn, &force_show_window])
.build()?; .build()?;
// Drop the reference now so we unlock immediately.
drop(button_ref);
let menu = MenuBuilder::new(app) let menu = MenuBuilder::new(app)
.items(&[ .items(&[
&spn,
&PredefinedMenuItem::separator(app),
&open_btn, &open_btn,
&close_btn, &PredefinedMenuItem::separator(app)?,
&global_status,
&PredefinedMenuItem::separator(app)?,
&spn_status,
&spn,
&PredefinedMenuItem::separator(app)?,
&exit_ui_btn,
&shutdown_btn,
&developer_menu, &developer_menu,
]) ])
.build()?; .build()?;
let icon = TrayIconBuilder::new() let icon = TrayIconBuilder::with_id(PM_TRAY_ICON_ID)
.icon(Icon::Raw(RED_ICON.to_vec())) .icon(Image::from_bytes(RED_ICON).unwrap())
.menu(&menu) .menu(&menu)
.on_menu_event(move |app, event| match event.id().as_ref() { .on_menu_event(move |app, event| match event.id().as_ref() {
"close" => { "exit_ui" => {
let handle = app.clone(); let handle = app.clone();
app.dialog() app.dialog()
.message("This does not stop the Portmaster system service") .message("This does not stop the Portmaster system service")
@ -90,7 +117,7 @@ pub fn setup_tray_menu(
.cancel_button_label("No") .cancel_button_label("No")
.show(move |answer| { .show(move |answer| {
if answer { if answer {
let _ = handle.emit("exit-requested", ""); // let _ = handle.emit("exit-requested", "");
handle.exit(0); handle.exit(0);
} }
}); });
@ -116,14 +143,15 @@ pub fn setup_tray_menu(
} }
}; };
} }
"spn" => { "spn_toggle" => {
let btn = SPN_BUTTON.lock().unwrap(); if SPN_STATE.load(Ordering::Acquire) {
app.portmaster().set_spn_enabled(false);
if let Some(bt) = &*btn { } else {
if let Ok(is_checked) = bt.is_checked() { app.portmaster().set_spn_enabled(true);
app.portmaster().set_spn_enabled(is_checked);
} }
} }
"shutdown" => {
app.portmaster().trigger_shutdown();
} }
other => { other => {
error!("unknown menu event id: {}", other); error!("unknown menu event id: {}", other);
@ -131,12 +159,23 @@ pub fn setup_tray_menu(
}) })
.on_tray_icon_event(|tray, event| { .on_tray_icon_event(|tray, event| {
// not supported on linux // not supported on linux
if event.click_type == ClickType::Left {
if let tauri::tray::TrayIconEvent::Click {
id: _,
position: _,
rect: _,
button,
button_state,
} = event
{
if let MouseButton::Left = button {
if let MouseButtonState::Down = button_state {
let _ = open_window(tray.app_handle()); let _ = open_window(tray.app_handle());
} }
}
}
}) })
.build(app)?; .build(app)?;
Ok(icon) Ok(icon)
} }
@ -145,19 +184,27 @@ pub fn update_icon(icon: AppIcon, subsystems: HashMap<String, Subsystem>, spn_st
let failure = subsystems let failure = subsystems
.values() .values()
.into_iter() .into_iter()
.map(|s| s.failure_status) .map(|s| &s.module_status)
.fold( .fold((subsystem::FAILURE_NONE, "".to_string()), |mut acc, s| {
subsystem::FAILURE_NONE, for m in s {
|acc, s| { if m.failure_status > acc.0 {
if s > acc { acc = (m.failure_status, m.failure_msg.clone())
s
} else {
acc
} }
}, }
); acc
});
let next_icon = match failure { if failure.0 == subsystem::FAILURE_NONE {
if let Some(global_status) = &mut *(GLOBAL_STATUS.lock().unwrap()) {
_ = global_status.set_text("Status: Secured");
}
} else {
if let Some(global_status) = &mut *(GLOBAL_STATUS.lock().unwrap()) {
_ = global_status.set_text(format!("Status: {}", failure.1));
}
}
let next_icon = match failure.0 {
subsystem::FAILURE_WARNING => YELLOW_ICON, subsystem::FAILURE_WARNING => YELLOW_ICON,
subsystem::FAILURE_ERROR => RED_ICON, subsystem::FAILURE_ERROR => RED_ICON,
_ => match spn_status.as_str() { _ => match spn_status.as_str() {
@ -166,11 +213,11 @@ pub fn update_icon(icon: AppIcon, subsystems: HashMap<String, Subsystem>, spn_st
}, },
}; };
_ = icon.set_icon(Some(Icon::Raw(next_icon.to_vec()))); _ = icon.set_icon(Some(Image::from_bytes(next_icon).unwrap()));
} }
pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) { pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
let icon = match app.tray() { let icon = match app.tray_by_id(PM_TRAY_ICON_ID) {
Some(icon) => icon, Some(icon) => icon,
None => { None => {
error!("cancel try_handler: missing try icon"); error!("cancel try_handler: missing try icon");
@ -226,7 +273,23 @@ pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
} }
}; };
_ = icon.set_icon(Some(Icon::Raw(BLUE_ICON.to_vec()))); let mut portmaster_shutdown_event_subscription = match cli
.request(Request::Subscribe(
"query runtime:modules/core/event/shutdown".to_string(),
))
.await
{
Ok(rx) => rx,
Err(err) => {
error!(
"cancel try_handler: failed to subscribe to 'runtime:modules/core/event/shutdown': {}",
err
);
return;
}
};
_ = icon.set_icon(Some(Image::from_bytes(BLUE_ICON).unwrap()));
let mut subsystems: HashMap<String, Subsystem> = HashMap::new(); let mut subsystems: HashMap<String, Subsystem> = HashMap::new();
let mut spn_status: String = "".to_string(); let mut spn_status: String = "".to_string();
@ -312,15 +375,7 @@ pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
if let Some((_, payload)) = res { if let Some((_, payload)) = res {
match payload.parse::<BooleanValue>() { match payload.parse::<BooleanValue>() {
Ok(value) => { Ok(value) => {
let mut btn = SPN_BUTTON.lock().unwrap(); update_spn_ui_state(value.value.unwrap_or(false));
if let Some(btn) = &mut *btn {
if let Some(value) = value.value {
_ = btn.set_checked(value);
} else {
_ = btn.set_checked(false);
}
}
}, },
Err(err) => match err { Err(err) => match err {
ParseError::JSON(err) => { ParseError::JSON(err) => {
@ -332,13 +387,40 @@ pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
} }
} }
} }
},
msg = portmaster_shutdown_event_subscription.recv() => {
let msg = match msg {
Some(m) => m,
None => { break }
};
debug!("Shutdown request received: {:?}", msg);
match msg {
Response::Ok(_, _) | Response::New(_, _) | Response::Update(_, _) => app.exit(0),
_ => {},
}
} }
} }
} }
if let Some(btn) = &mut *(SPN_BUTTON.lock().unwrap()) { update_spn_ui_state(false);
_ = btn.set_checked(false); _ = icon.set_icon(Some(Image::from_bytes(RED_ICON).unwrap()));
} }
_ = icon.set_icon(Some(Icon::Raw(RED_ICON.to_vec()))); fn update_spn_ui_state(enabled: bool) {
let mut spn_status = SPN_STATUS.lock().unwrap();
let Some(spn_status_ref) = &mut *spn_status else {
return;
};
let mut spn_btn = SPN_BUTTON.lock().unwrap();
let Some(spn_btn_ref) = &mut *spn_btn else {
return;
};
if enabled {
_ = spn_status_ref.set_text("SPN: Connected");
_ = spn_btn_ref.set_text("Disable SPN");
} else {
_ = spn_status_ref.set_text("SPN: Disabled");
_ = spn_btn_ref.set_text("Enable SPN");
}
SPN_STATE.store(enabled, Ordering::SeqCst);
} }

View file

@ -1,5 +1,7 @@
use log::{debug, error}; use log::{debug, error};
use tauri::{AppHandle, Manager, Result, UserAttentionType, Window, WindowBuilder, WindowUrl}; use tauri::{
AppHandle, Manager, Result, UserAttentionType, WebviewUrl, WebviewWindow, WebviewWindowBuilder,
};
use crate::portmaster::PortmasterExt; use crate::portmaster::PortmasterExt;
@ -11,15 +13,16 @@ use crate::portmaster::PortmasterExt;
/// if ::websocket::is_portapi_reachable returns true. /// if ::websocket::is_portapi_reachable returns true.
/// ///
/// Either the existing or the newly created window is returned. /// Either the existing or the newly created window is returned.
pub fn create_main_window(app: &AppHandle) -> Result<Window> { pub fn create_main_window(app: &AppHandle) -> Result<WebviewWindow> {
let mut window = if let Some(window) = app.get_window("main") { let mut window = if let Some(window) = app.get_webview_window("main") {
debug!("[tauri] main window already created"); debug!("[tauri] main window already created");
window window
} else { } else {
debug!("[tauri] creating main window"); debug!("[tauri] creating main window");
let res = WindowBuilder::new(app, "main", WindowUrl::App("index.html".into())) let res = WebviewWindowBuilder::new(app, "main", WebviewUrl::App("index.html".into()))
.title("Portmaster")
.visible(false) .visible(false)
.build(); .build();
@ -54,12 +57,12 @@ pub fn create_main_window(app: &AppHandle) -> Result<Window> {
Ok(window) Ok(window)
} }
pub fn create_splash_window(app: &AppHandle) -> Result<Window> { pub fn create_splash_window(app: &AppHandle) -> Result<WebviewWindow> {
if let Some(window) = app.get_window("splash") { if let Some(window) = app.get_webview_window("splash") {
let _ = window.show(); let _ = window.show();
Ok(window) Ok(window)
} else { } else {
let window = WindowBuilder::new(app, "splash", WindowUrl::App("index.html".into())) let window = WebviewWindowBuilder::new(app, "splash", WebviewUrl::App("index.html".into()))
.center() .center()
.closable(false) .closable(false)
.focused(true) .focused(true)
@ -76,8 +79,9 @@ pub fn create_splash_window(app: &AppHandle) -> Result<Window> {
} }
pub fn close_splash_window(app: &AppHandle) -> Result<()> { pub fn close_splash_window(app: &AppHandle) -> Result<()> {
if let Some(window) = app.get_window("splash") { if let Some(window) = app.get_webview_window("splash") {
return window.close(); let _ = window.hide();
return window.destroy();
} }
return Err(tauri::Error::WindowNotFound); return Err(tauri::Error::WindowNotFound);
} }
@ -94,9 +98,9 @@ pub fn close_splash_window(app: &AppHandle) -> Result<()> {
/// ///
/// If the Portmaster API is unreachable and there's no main window yet, we show the /// If the Portmaster API is unreachable and there's no main window yet, we show the
/// splash-screen window. /// splash-screen window.
pub fn open_window(app: &AppHandle) -> Result<Window> { pub fn open_window(app: &AppHandle) -> Result<WebviewWindow> {
if app.portmaster().is_reachable() { if app.portmaster().is_reachable() {
match app.get_window("main") { match app.get_webview_window("main") {
Some(win) => { Some(win) => {
app.portmaster().show_window(); app.portmaster().show_window();
@ -122,32 +126,41 @@ pub fn open_window(app: &AppHandle) -> Result<Window> {
/// In #[cfg(debug_assertions)] the TAURI_PM_URL environment variable will be used /// In #[cfg(debug_assertions)] the TAURI_PM_URL environment variable will be used
/// if set. /// if set.
/// Otherwise or in release builds, it will be navigated to http://127.0.0.1:817. /// Otherwise or in release builds, it will be navigated to http://127.0.0.1:817.
pub fn may_navigate_to_ui(win: &mut Window, force: bool) { pub fn may_navigate_to_ui(win: &mut WebviewWindow, force: bool) {
if !win.app_handle().portmaster().is_reachable() && !force { if !win.app_handle().portmaster().is_reachable() && !force {
error!("[tauri] portmaster API is not reachable, not navigating"); error!("[tauri] portmaster API is not reachable, not navigating");
return; return;
} }
if force || win.label().eq("main") {
if force || cfg!(debug_assertions) || win.url().as_str() == "tauri://localhost" {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
if let Ok(target_url) = std::env::var("TAURI_PM_URL") { if let Ok(target_url) = std::env::var("TAURI_PM_URL") {
debug!("[tauri] navigating to {}", target_url); debug!("[tauri] navigating to {}", target_url);
win.navigate(target_url.parse().unwrap()); _ = win.navigate(target_url.parse().unwrap());
return; return;
} }
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
{ {
// Only for dev build
// Allow connection to http://localhost:4200
let capabilities = include_str!("../capabilities/default.json")
.replace("http://localhost:817", "http://localhost:4200");
let _ = win.add_capability(capabilities);
debug!("[tauri] navigating to http://localhost:4200"); debug!("[tauri] navigating to http://localhost:4200");
win.navigate("http://localhost:4200".parse().unwrap()); _ = win.navigate("http://localhost:4200".parse().unwrap());
} }
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
win.navigate("http://localhost:817".parse().unwrap()); {
_ = win.navigate("http://localhost:817".parse().unwrap());
}
} else { } else {
error!("not navigating to user interface: current url: {}", win.url().as_str()); error!(
"not navigating to user interface: current url: {}",
win.url().unwrap().as_str()
);
} }
} }

View file

@ -1,116 +0,0 @@
{
"build": {
"beforeDevCommand": {
"script": "npm run tauri-dev",
"cwd": "../../angular",
"wait": false
},
"devPath": "http://localhost:4100",
"distDir": "../../angular/dist/tauri-builtin",
"withGlobalTauri": true
},
"package": {
"productName": "Portmaster",
"version": "0.1.0"
},
"plugins": {
"cli": {
"args": [
{
"short": "d",
"name": "data",
"description": "Path to the installation directory",
"takesValue": true
},
{
"short": "b",
"name": "background",
"description": "Start in the background without opening a window"
},
{
"name": "with-notifications",
"description": "Enable experimental notifications via Tauri. Replaces the notifier app."
},
{
"name": "with-prompts",
"description": "Enable experimental prompt support via Tauri. Replaces the notifier app."
}
]
}
},
"tauri": {
"bundle": {
"active": true,
"category": "Utility",
"copyright": "Safing Limited Inc",
"deb": {
"depends": [
"libayatana-appindicator3"
],
"desktopTemplate": "../../../packaging/linux/portmaster.desktop",
"files": {
"/usr/lib/systemd/system/portmaster.service": "../../../packaging/linux/portmaster.service",
"/etc/xdg/autostart/portmaster.desktop": "../../../packaging/linux/portmaster-autostart.desktop",
"/var/": "../../../packaging/linux/var",
"../control/postinst": "../../../packaging/linux/debian/postinst",
"../control/postrm": "../../../packaging/linux/debian/postrm"
}
},
"externalBin": [
"binaries/portmaster-start",
"binaries/portmaster-core"
],
"icon": [
"../assets/icons/pm_dark_512.png",
"../assets/icons/pm_dark_512.ico",
"../assets/icons/pm_light_512.png",
"../assets/icons/pm_light_512.ico"
],
"identifier": "io.safing.portmaster",
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": [
"deb",
"appimage",
"nsis",
"msi",
"app"
],
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"security": {
"csp": null,
"dangerousRemoteDomainIpcAccess": [
{
"windows": [
"main",
"prompt"
],
"plugins": [
"shell",
"os",
"clipboard-manager",
"event",
"window",
"cli",
"portmaster"
],
"domain": "localhost"
}
]
},
"windows": []
}
}

View file

@ -0,0 +1,106 @@
{
"build": {
"beforeDevCommand": {
"script": "npm run tauri-dev",
"cwd": "../../angular",
"wait": false
},
"frontendDist": "../../angular/dist/tauri-builtin",
"devUrl": "http://localhost:4100"
},
"plugins": {
"cli": {
"args": [
{
"short": "d",
"name": "data",
"description": "Path to the installation directory",
"takesValue": true
},
{
"short": "b",
"name": "background",
"description": "Start in the background without opening a window"
},
{
"name": "with-notifications",
"description": "Enable experimental notifications via Tauri. Replaces the notifier app."
},
{
"name": "with-prompts",
"description": "Enable experimental prompt support via Tauri. Replaces the notifier app."
}
]
}
},
"productName": "Portmaster",
"version": "0.1.0",
"identifier": "io.safing.portmaster", // this is added as a property to the shortcut on windows (ApplicationUserModelID). Used for notifications.
"app": {
"withGlobalTauri": true,
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"category": "Utility",
"copyright": "Safing Limited Inc",
"linux": {
"deb": {
"depends": [
"libayatana-appindicator3"
],
"desktopTemplate": "../../../packaging/linux/portmaster.desktop",
"files": {
"/usr/lib/systemd/system/portmaster.service": "../../../packaging/linux/portmaster.service",
"/etc/xdg/autostart/portmaster.desktop": "../../../packaging/linux/portmaster-autostart.desktop"
},
"postInstallScript": "../../../packaging/linux/postinst",
"postRemoveScript": "../../../packaging/linux/postrm"
},
"rpm": {
"depends": [
"libayatana-appindicator-gtk3"
],
"desktopTemplate": "../../../packaging/linux/portmaster.desktop",
"release": "1",
"files": {
"/usr/lib/systemd/system/portmaster.service": "../../../packaging/linux/portmaster.service",
"/etc/xdg/autostart/portmaster.desktop": "../../../packaging/linux/portmaster-autostart.desktop"
},
"postInstallScript": "../../../packaging/linux/postinst",
"postRemoveScript": "../../../packaging/linux/postrm"
}
},
"windows": {
"nsis": {
"installMode": "perMachine",
"installerHooks": "templates/nsis_install_hooks.nsh",
"installerIcon": "../../../assets/data/icons/pm_light.ico"
},
"wix": {
"fragmentPaths": [
"templates/service.wxs"
],
"template": "templates/main.wxs"
}
},
"externalBin": [
"binaries/portmaster-start",
"binaries/portmaster-core"
],
"targets": [
"deb",
"rpm",
"nsis",
"msi"
],
"icon": [
"../../../assets/data/icons/pm_dark_512.png",
"../../../assets/data/icons/pm_dark.ico",
"../../../assets/data/icons/pm_light_512.png",
"../../../assets/data/icons/pm_light.ico"
]
}
}

View file

@ -0,0 +1,354 @@
<?if $(sys.BUILDARCH)="x86"?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?elseif $(sys.BUILDARCH)="x64"?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?elseif $(sys.BUILDARCH)="arm64"?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else?>
<?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?>
<?endif?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product
Id="*"
Name="{{product_name}}"
UpgradeCode="{{upgrade_code}}"
Language="!(loc.TauriLanguage)"
Manufacturer="{{manufacturer}}"
Version="{{version}}">
<Package Id="*"
Keywords="Installer"
InstallerVersion="450"
Languages="0"
Compressed="yes"
InstallScope="perMachine"
SummaryCodepage="!(loc.TauriCodepage)"/>
<!-- https://docs.microsoft.com/en-us/windows/win32/msi/reinstallmode -->
<!-- reinstall all files; rewrite all registry entries; reinstall all shortcuts -->
<Property Id="REINSTALLMODE" Value="amus" />
<!-- Auto launch app after installation, useful for passive mode which usually used in updates -->
<Property Id="AUTOLAUNCHAPP" Secure="yes" />
<!-- Property to forward cli args to the launched app to not lose those of the pre-update instance -->
<Property Id="LAUNCHAPPARGS" Secure="yes" />
{{#if allow_downgrades}}
<MajorUpgrade Schedule="afterInstallInitialize" AllowDowngrades="yes" />
{{else}}
<MajorUpgrade Schedule="afterInstallInitialize" DowngradeErrorMessage="!(loc.DowngradeErrorMessage)" AllowSameVersionUpgrades="yes" />
{{/if}}
<InstallExecuteSequence>
<RemoveShortcuts>Installed AND NOT UPGRADINGPRODUCTCODE</RemoveShortcuts>
</InstallExecuteSequence>
<Media Id="1" Cabinet="app.cab" EmbedCab="yes" />
{{#if banner_path}}
<WixVariable Id="WixUIBannerBmp" Value="{{banner_path}}" />
{{/if}}
{{#if dialog_image_path}}
<WixVariable Id="WixUIDialogBmp" Value="{{dialog_image_path}}" />
{{/if}}
{{#if license}}
<WixVariable Id="WixUILicenseRtf" Value="{{license}}" />
{{/if}}
<Icon Id="ProductIcon" SourceFile="{{icon_path}}"/>
<Property Id="ARPPRODUCTICON" Value="ProductIcon" />
<Property Id="ARPNOREPAIR" Value="yes" Secure="yes" /> <!-- Remove repair -->
<SetProperty Id="ARPNOMODIFY" Value="1" After="InstallValidate" Sequence="execute"/>
{{#if homepage}}
<Property Id="ARPURLINFOABOUT" Value="{{homepage}}"/>
<Property Id="ARPHELPLINK" Value="{{homepage}}"/>
<Property Id="ARPURLUPDATEINFO" Value="{{homepage}}"/>
{{/if}}
<!-- initialize with previous InstallDir -->
<Property Id="INSTALLDIR">
<RegistrySearch Id="PrevInstallDirReg" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="InstallDir" Type="raw"/>
</Property>
<!-- launch app checkbox -->
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="!(loc.LaunchApp)" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>
<CustomAction Id="LaunchApplication" Impersonate="yes" FileKey="Path" ExeCommand="[LAUNCHAPPARGS]" Return="asyncNoWait" />
<UI>
<!-- launch app checkbox -->
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
{{#unless license}}
<!-- Skip license dialog -->
<Publish Dialog="WelcomeDlg"
Control="Next"
Event="NewDialog"
Value="InstallDirDlg"
Order="2">1</Publish>
<Publish Dialog="InstallDirDlg"
Control="Back"
Event="NewDialog"
Value="WelcomeDlg"
Order="2">1</Publish>
{{/unless}}
</UI>
<UIRef Id="WixUI_InstallDir" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="DesktopFolder" Name="Desktop">
<Component Id="ApplicationShortcutDesktop" Guid="*">
<Shortcut Id="ApplicationDesktopShortcut" Name="{{product_name}}" Description="Runs {{product_name}}" Target="[!Path]" WorkingDirectory="INSTALLDIR" />
<RemoveFolder Id="DesktopFolder" On="uninstall" />
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Desktop Shortcut" Type="integer" Value="1" KeyPath="yes" />
</Component>
</Directory>
<Directory Id="$(var.PlatformProgramFilesFolder)" Name="PFiles">
<Directory Id="INSTALLDIR" Name="{{product_name}}"/>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ApplicationProgramsFolder" Name="{{product_name}}"/>
</Directory>
</Directory>
<DirectoryRef Id="INSTALLDIR">
<Component Id="RegistryEntries" Guid="*">
<RegistryKey Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}">
<RegistryValue Name="InstallDir" Type="string" Value="[INSTALLDIR]" KeyPath="yes" />
</RegistryKey>
<!-- Change the Root to HKCU for perUser installations -->
{{#each deep_link_protocols as |protocol| ~}}
<RegistryKey Root="HKLM" Key="Software\Classes\\{{protocol}}">
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
<RegistryValue Type="string" Value="URL:{{bundle_id}} protocol"/>
<RegistryKey Key="DefaultIcon">
<RegistryValue Type="string" Value="&quot;[!Path]&quot;,0" />
</RegistryKey>
<RegistryKey Key="shell\open\command">
<RegistryValue Type="string" Value="&quot;[!Path]&quot; &quot;%1&quot;" />
</RegistryKey>
</RegistryKey>
{{/each~}}
</Component>
<Component Id="Path" Guid="{{path_component_guid}}" Win64="$(var.Win64)">
<File Id="Path" Source="{{app_exe_source}}" KeyPath="yes" Checksum="yes"/>
{{#each file_associations as |association| ~}}
{{#each association.ext as |ext| ~}}
<ProgId Id="{{../../product_name}}.{{ext}}" Advertise="yes" Description="{{association.description}}">
<Extension Id="{{ext}}" Advertise="yes">
<Verb Id="open" Command="Open with {{../../product_name}}" Argument="&quot;%1&quot;" />
</Extension>
</ProgId>
{{/each~}}
{{/each~}}
</Component>
{{#each binaries as |bin| ~}}
<Component Id="{{ bin.id }}" Guid="{{bin.guid}}" Win64="$(var.Win64)">
<File Id="Bin_{{ bin.id }}" Source="{{bin.path}}" KeyPath="yes"/>
</Component>
{{/each~}}
{{#if enable_elevated_update_task}}
<Component Id="UpdateTask" Guid="C492327D-9720-4CD5-8DB8-F09082AF44BE" Win64="$(var.Win64)">
<File Id="UpdateTask" Source="update.xml" KeyPath="yes" Checksum="yes"/>
</Component>
<Component Id="UpdateTaskInstaller" Guid="011F25ED-9BE3-50A7-9E9B-3519ED2B9932" Win64="$(var.Win64)">
<File Id="UpdateTaskInstaller" Source="install-task.ps1" KeyPath="yes" Checksum="yes"/>
</Component>
<Component Id="UpdateTaskUninstaller" Guid="D4F6CC3F-32DC-5FD0-95E8-782FFD7BBCE1" Win64="$(var.Win64)">
<File Id="UpdateTaskUninstaller" Source="uninstall-task.ps1" KeyPath="yes" Checksum="yes"/>
</Component>
{{/if}}
{{resources}}
<Component Id="CMP_UninstallShortcut" Guid="*">
<Shortcut Id="UninstallShortcut"
Name="Uninstall {{product_name}}"
Description="Uninstalls {{product_name}}"
Target="[System64Folder]msiexec.exe"
Arguments="/x [ProductCode]" />
<RemoveFolder Id="INSTALLDIR"
On="uninstall" />
<RegistryValue Root="HKCU"
Key="Software\\{{manufacturer}}\\{{product_name}}"
Name="Uninstaller Shortcut"
Type="integer"
Value="1"
KeyPath="yes" />
</Component>
</DirectoryRef>
<DirectoryRef Id="ApplicationProgramsFolder">
<Component Id="ApplicationShortcut" Guid="*">
<Shortcut Id="ApplicationStartMenuShortcut"
Name="{{product_name}}"
Description="Runs {{product_name}}"
Target="[!Path]"
Icon="ProductIcon"
WorkingDirectory="INSTALLDIR">
<ShortcutProperty Key="System.AppUserModel.ID" Value="{{bundle_id}}"/>
</Shortcut>
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Start Menu Shortcut" Type="integer" Value="1" KeyPath="yes"/>
</Component>
</DirectoryRef>
{{#each merge_modules as |msm| ~}}
<DirectoryRef Id="TARGETDIR">
<Merge Id="{{ msm.name }}" SourceFile="{{ msm.path }}" DiskId="1" Language="!(loc.TauriLanguage)" />
</DirectoryRef>
<Feature Id="{{ msm.name }}" Title="{{ msm.name }}" AllowAdvertise="no" Display="hidden" Level="1">
<MergeRef Id="{{ msm.name }}"/>
</Feature>
{{/each~}}
<Feature
Id="MainProgram"
Title="Application"
Description="!(loc.InstallAppFeature)"
Level="1"
ConfigurableDirectory="INSTALLDIR"
AllowAdvertise="no"
Display="expand"
Absent="disallow">
<ComponentRef Id="RegistryEntries"/>
{{#each resource_file_ids as |resource_file_id| ~}}
<ComponentRef Id="{{ resource_file_id }}"/>
{{/each~}}
{{#if enable_elevated_update_task}}
<ComponentRef Id="UpdateTask" />
<ComponentRef Id="UpdateTaskInstaller" />
<ComponentRef Id="UpdateTaskUninstaller" />
{{/if}}
<Feature Id="ShortcutsFeature"
Title="Shortcuts"
Level="1">
<ComponentRef Id="Path"/>
<ComponentRef Id="CMP_UninstallShortcut" />
<ComponentRef Id="ApplicationShortcut" />
<ComponentRef Id="ApplicationShortcutDesktop" />
</Feature>
<Feature
Id="Environment"
Title="PATH Environment Variable"
Description="!(loc.PathEnvVarFeature)"
Level="1"
Absent="allow">
<ComponentRef Id="Path"/>
{{#each binaries as |bin| ~}}
<ComponentRef Id="{{ bin.id }}"/>
{{/each~}}
</Feature>
</Feature>
<Feature Id="External" AllowAdvertise="no" Absent="disallow">
{{#each component_group_refs as |id| ~}}
<ComponentGroupRef Id="{{ id }}"/>
{{/each~}}
{{#each component_refs as |id| ~}}
<ComponentRef Id="{{ id }}"/>
{{/each~}}
{{#each feature_group_refs as |id| ~}}
<FeatureGroupRef Id="{{ id }}"/>
{{/each~}}
{{#each feature_refs as |id| ~}}
<FeatureRef Id="{{ id }}"/>
{{/each~}}
{{#each merge_refs as |id| ~}}
<MergeRef Id="{{ id }}"/>
{{/each~}}
</Feature>
{{#if install_webview}}
<!-- WebView2 -->
<Property Id="WVRTINSTALLED">
<RegistrySearch Id="WVRTInstalledSystem" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" Win64="no" />
<RegistrySearch Id="WVRTInstalledUser" Root="HKCU" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw"/>
</Property>
{{#if download_bootstrapper}}
<CustomAction Id='DownloadAndInvokeBootstrapper' Directory="INSTALLDIR" Execute="deferred" ExeCommand='powershell.exe -NoProfile -windowstyle hidden try [\{] [\[]Net.ServicePointManager[\]]::SecurityProtocol = [\[]Net.SecurityProtocolType[\]]::Tls12 [\}] catch [\{][\}]; Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" ; Start-Process -FilePath "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" -ArgumentList ({{webview_installer_args}} &apos;/install&apos;) -Wait' Return='check'/>
<InstallExecuteSequence>
<Custom Action='DownloadAndInvokeBootstrapper' Before='InstallFinalize'>
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
</Custom>
</InstallExecuteSequence>
{{/if}}
<!-- Embedded webview bootstrapper mode -->
{{#if webview2_bootstrapper_path}}
<Binary Id="MicrosoftEdgeWebview2Setup.exe" SourceFile="{{webview2_bootstrapper_path}}"/>
<CustomAction Id='InvokeBootstrapper' BinaryKey='MicrosoftEdgeWebview2Setup.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
<InstallExecuteSequence>
<Custom Action='InvokeBootstrapper' Before='InstallFinalize'>
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
</Custom>
</InstallExecuteSequence>
{{/if}}
<!-- Embedded offline installer -->
{{#if webview2_installer_path}}
<Binary Id="MicrosoftEdgeWebView2RuntimeInstaller.exe" SourceFile="{{webview2_installer_path}}"/>
<CustomAction Id='InvokeStandalone' BinaryKey='MicrosoftEdgeWebView2RuntimeInstaller.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
<InstallExecuteSequence>
<Custom Action='InvokeStandalone' Before='InstallFinalize'>
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
</Custom>
</InstallExecuteSequence>
{{/if}}
{{/if}}
{{#if enable_elevated_update_task}}
<!-- Install an elevated update task within Windows Task Scheduler -->
<CustomAction
Id="CreateUpdateTask"
Return="check"
Directory="INSTALLDIR"
Execute="commit"
Impersonate="yes"
ExeCommand="powershell.exe -WindowStyle hidden .\install-task.ps1" />
<InstallExecuteSequence>
<Custom Action='CreateUpdateTask' Before='InstallFinalize'>
NOT(REMOVE)
</Custom>
</InstallExecuteSequence>
<!-- Remove elevated update task during uninstall -->
<CustomAction
Id="DeleteUpdateTask"
Return="check"
Directory="INSTALLDIR"
ExeCommand="powershell.exe -WindowStyle hidden .\uninstall-task.ps1" />
<InstallExecuteSequence>
<Custom Action="DeleteUpdateTask" Before='InstallFinalize'>
(REMOVE = "ALL") AND NOT UPGRADINGPRODUCTCODE
</Custom>
</InstallExecuteSequence>
{{/if}}
<CustomActionRef Id='InstallPortmasterService' />
<CustomActionRef Id='StopPortmasterService' />
<CustomActionRef Id='DeletePortmasterService' />
<InstallExecuteSequence>
<Custom Action="LaunchApplication" After="InstallFinalize">AUTOLAUNCHAPP AND NOT Installed</Custom>
</InstallExecuteSequence>
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLDIR]" After="CostFinalize"/>
</Product>
</Wix>

View file

@ -0,0 +1,350 @@
<?if $(sys.BUILDARCH)="x86"?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?elseif $(sys.BUILDARCH)="x64"?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?elseif $(sys.BUILDARCH)="arm64"?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else?>
<?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?>
<?endif?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product
Id="*"
Name="{{product_name}}"
UpgradeCode="{{upgrade_code}}"
Language="!(loc.TauriLanguage)"
Manufacturer="{{manufacturer}}"
Version="{{version}}">
<Package Id="*"
Keywords="Installer"
InstallerVersion="450"
Languages="0"
Compressed="yes"
InstallScope="perMachine"
SummaryCodepage="!(loc.TauriCodepage)"/>
<!-- https://docs.microsoft.com/en-us/windows/win32/msi/reinstallmode -->
<!-- reinstall all files; rewrite all registry entries; reinstall all shortcuts -->
<Property Id="REINSTALLMODE" Value="amus" />
<!-- Auto launch app after installation, useful for passive mode which usually used in updates -->
<Property Id="AUTOLAUNCHAPP" Secure="yes" />
<!-- Property to forward cli args to the launched app to not lose those of the pre-update instance -->
<Property Id="LAUNCHAPPARGS" Secure="yes" />
{{#if allow_downgrades}}
<MajorUpgrade Schedule="afterInstallInitialize" AllowDowngrades="yes" />
{{else}}
<MajorUpgrade Schedule="afterInstallInitialize" DowngradeErrorMessage="!(loc.DowngradeErrorMessage)" AllowSameVersionUpgrades="yes" />
{{/if}}
<InstallExecuteSequence>
<RemoveShortcuts>Installed AND NOT UPGRADINGPRODUCTCODE</RemoveShortcuts>
</InstallExecuteSequence>
<Media Id="1" Cabinet="app.cab" EmbedCab="yes" />
{{#if banner_path}}
<WixVariable Id="WixUIBannerBmp" Value="{{banner_path}}" />
{{/if}}
{{#if dialog_image_path}}
<WixVariable Id="WixUIDialogBmp" Value="{{dialog_image_path}}" />
{{/if}}
{{#if license}}
<WixVariable Id="WixUILicenseRtf" Value="{{license}}" />
{{/if}}
<Icon Id="ProductIcon" SourceFile="{{icon_path}}"/>
<Property Id="ARPPRODUCTICON" Value="ProductIcon" />
<Property Id="ARPNOREPAIR" Value="yes" Secure="yes" /> <!-- Remove repair -->
<SetProperty Id="ARPNOMODIFY" Value="1" After="InstallValidate" Sequence="execute"/>
{{#if homepage}}
<Property Id="ARPURLINFOABOUT" Value="{{homepage}}"/>
<Property Id="ARPHELPLINK" Value="{{homepage}}"/>
<Property Id="ARPURLUPDATEINFO" Value="{{homepage}}"/>
{{/if}}
<!-- initialize with previous InstallDir -->
<Property Id="INSTALLDIR">
<RegistrySearch Id="PrevInstallDirReg" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="InstallDir" Type="raw"/>
</Property>
<!-- launch app checkbox -->
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="!(loc.LaunchApp)" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>
<CustomAction Id="LaunchApplication" Impersonate="yes" FileKey="Path" ExeCommand="[LAUNCHAPPARGS]" Return="asyncNoWait" />
<UI>
<!-- launch app checkbox -->
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
{{#unless license}}
<!-- Skip license dialog -->
<Publish Dialog="WelcomeDlg"
Control="Next"
Event="NewDialog"
Value="InstallDirDlg"
Order="2">1</Publish>
<Publish Dialog="InstallDirDlg"
Control="Back"
Event="NewDialog"
Value="WelcomeDlg"
Order="2">1</Publish>
{{/unless}}
</UI>
<UIRef Id="WixUI_InstallDir" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="DesktopFolder" Name="Desktop">
<Component Id="ApplicationShortcutDesktop" Guid="*">
<Shortcut Id="ApplicationDesktopShortcut" Name="{{product_name}}" Description="Runs {{product_name}}" Target="[!Path]" WorkingDirectory="INSTALLDIR" />
<RemoveFolder Id="DesktopFolder" On="uninstall" />
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Desktop Shortcut" Type="integer" Value="1" KeyPath="yes" />
</Component>
</Directory>
<Directory Id="$(var.PlatformProgramFilesFolder)" Name="PFiles">
<Directory Id="INSTALLDIR" Name="{{product_name}}"/>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ApplicationProgramsFolder" Name="{{product_name}}"/>
</Directory>
</Directory>
<DirectoryRef Id="INSTALLDIR">
<Component Id="RegistryEntries" Guid="*">
<RegistryKey Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}">
<RegistryValue Name="InstallDir" Type="string" Value="[INSTALLDIR]" KeyPath="yes" />
</RegistryKey>
<!-- Change the Root to HKCU for perUser installations -->
{{#each deep_link_protocols as |protocol| ~}}
<RegistryKey Root="HKLM" Key="Software\Classes\\{{protocol}}">
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
<RegistryValue Type="string" Value="URL:{{bundle_id}} protocol"/>
<RegistryKey Key="DefaultIcon">
<RegistryValue Type="string" Value="&quot;[!Path]&quot;,0" />
</RegistryKey>
<RegistryKey Key="shell\open\command">
<RegistryValue Type="string" Value="&quot;[!Path]&quot; &quot;%1&quot;" />
</RegistryKey>
</RegistryKey>
{{/each~}}
</Component>
<Component Id="Path" Guid="{{path_component_guid}}" Win64="$(var.Win64)">
<File Id="Path" Source="{{app_exe_source}}" KeyPath="yes" Checksum="yes"/>
{{#each file_associations as |association| ~}}
{{#each association.ext as |ext| ~}}
<ProgId Id="{{../../product_name}}.{{ext}}" Advertise="yes" Description="{{association.description}}">
<Extension Id="{{ext}}" Advertise="yes">
<Verb Id="open" Command="Open with {{../../product_name}}" Argument="&quot;%1&quot;" />
</Extension>
</ProgId>
{{/each~}}
{{/each~}}
</Component>
{{#each binaries as |bin| ~}}
<Component Id="{{ bin.id }}" Guid="{{bin.guid}}" Win64="$(var.Win64)">
<File Id="Bin_{{ bin.id }}" Source="{{bin.path}}" KeyPath="yes"/>
</Component>
{{/each~}}
{{#if enable_elevated_update_task}}
<Component Id="UpdateTask" Guid="C492327D-9720-4CD5-8DB8-F09082AF44BE" Win64="$(var.Win64)">
<File Id="UpdateTask" Source="update.xml" KeyPath="yes" Checksum="yes"/>
</Component>
<Component Id="UpdateTaskInstaller" Guid="011F25ED-9BE3-50A7-9E9B-3519ED2B9932" Win64="$(var.Win64)">
<File Id="UpdateTaskInstaller" Source="install-task.ps1" KeyPath="yes" Checksum="yes"/>
</Component>
<Component Id="UpdateTaskUninstaller" Guid="D4F6CC3F-32DC-5FD0-95E8-782FFD7BBCE1" Win64="$(var.Win64)">
<File Id="UpdateTaskUninstaller" Source="uninstall-task.ps1" KeyPath="yes" Checksum="yes"/>
</Component>
{{/if}}
{{resources}}
<Component Id="CMP_UninstallShortcut" Guid="*">
<Shortcut Id="UninstallShortcut"
Name="Uninstall {{product_name}}"
Description="Uninstalls {{product_name}}"
Target="[System64Folder]msiexec.exe"
Arguments="/x [ProductCode]" />
<RemoveFolder Id="INSTALLDIR"
On="uninstall" />
<RegistryValue Root="HKCU"
Key="Software\\{{manufacturer}}\\{{product_name}}"
Name="Uninstaller Shortcut"
Type="integer"
Value="1"
KeyPath="yes" />
</Component>
</DirectoryRef>
<DirectoryRef Id="ApplicationProgramsFolder">
<Component Id="ApplicationShortcut" Guid="*">
<Shortcut Id="ApplicationStartMenuShortcut"
Name="{{product_name}}"
Description="Runs {{product_name}}"
Target="[!Path]"
Icon="ProductIcon"
WorkingDirectory="INSTALLDIR">
<ShortcutProperty Key="System.AppUserModel.ID" Value="{{bundle_id}}"/>
</Shortcut>
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Start Menu Shortcut" Type="integer" Value="1" KeyPath="yes"/>
</Component>
</DirectoryRef>
{{#each merge_modules as |msm| ~}}
<DirectoryRef Id="TARGETDIR">
<Merge Id="{{ msm.name }}" SourceFile="{{ msm.path }}" DiskId="1" Language="!(loc.TauriLanguage)" />
</DirectoryRef>
<Feature Id="{{ msm.name }}" Title="{{ msm.name }}" AllowAdvertise="no" Display="hidden" Level="1">
<MergeRef Id="{{ msm.name }}"/>
</Feature>
{{/each~}}
<Feature
Id="MainProgram"
Title="Application"
Description="!(loc.InstallAppFeature)"
Level="1"
ConfigurableDirectory="INSTALLDIR"
AllowAdvertise="no"
Display="expand"
Absent="disallow">
<ComponentRef Id="RegistryEntries"/>
{{#each resource_file_ids as |resource_file_id| ~}}
<ComponentRef Id="{{ resource_file_id }}"/>
{{/each~}}
{{#if enable_elevated_update_task}}
<ComponentRef Id="UpdateTask" />
<ComponentRef Id="UpdateTaskInstaller" />
<ComponentRef Id="UpdateTaskUninstaller" />
{{/if}}
<Feature Id="ShortcutsFeature"
Title="Shortcuts"
Level="1">
<ComponentRef Id="Path"/>
<ComponentRef Id="CMP_UninstallShortcut" />
<ComponentRef Id="ApplicationShortcut" />
<ComponentRef Id="ApplicationShortcutDesktop" />
</Feature>
<Feature
Id="Environment"
Title="PATH Environment Variable"
Description="!(loc.PathEnvVarFeature)"
Level="1"
Absent="allow">
<ComponentRef Id="Path"/>
{{#each binaries as |bin| ~}}
<ComponentRef Id="{{ bin.id }}"/>
{{/each~}}
</Feature>
</Feature>
<Feature Id="External" AllowAdvertise="no" Absent="disallow">
{{#each component_group_refs as |id| ~}}
<ComponentGroupRef Id="{{ id }}"/>
{{/each~}}
{{#each component_refs as |id| ~}}
<ComponentRef Id="{{ id }}"/>
{{/each~}}
{{#each feature_group_refs as |id| ~}}
<FeatureGroupRef Id="{{ id }}"/>
{{/each~}}
{{#each feature_refs as |id| ~}}
<FeatureRef Id="{{ id }}"/>
{{/each~}}
{{#each merge_refs as |id| ~}}
<MergeRef Id="{{ id }}"/>
{{/each~}}
</Feature>
{{#if install_webview}}
<!-- WebView2 -->
<Property Id="WVRTINSTALLED">
<RegistrySearch Id="WVRTInstalledSystem" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" Win64="no" />
<RegistrySearch Id="WVRTInstalledUser" Root="HKCU" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw"/>
</Property>
{{#if download_bootstrapper}}
<CustomAction Id='DownloadAndInvokeBootstrapper' Directory="INSTALLDIR" Execute="deferred" ExeCommand='powershell.exe -NoProfile -windowstyle hidden try [\{] [\[]Net.ServicePointManager[\]]::SecurityProtocol = [\[]Net.SecurityProtocolType[\]]::Tls12 [\}] catch [\{][\}]; Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" ; Start-Process -FilePath "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" -ArgumentList ({{webview_installer_args}} &apos;/install&apos;) -Wait' Return='check'/>
<InstallExecuteSequence>
<Custom Action='DownloadAndInvokeBootstrapper' Before='InstallFinalize'>
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
</Custom>
</InstallExecuteSequence>
{{/if}}
<!-- Embedded webview bootstrapper mode -->
{{#if webview2_bootstrapper_path}}
<Binary Id="MicrosoftEdgeWebview2Setup.exe" SourceFile="{{webview2_bootstrapper_path}}"/>
<CustomAction Id='InvokeBootstrapper' BinaryKey='MicrosoftEdgeWebview2Setup.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
<InstallExecuteSequence>
<Custom Action='InvokeBootstrapper' Before='InstallFinalize'>
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
</Custom>
</InstallExecuteSequence>
{{/if}}
<!-- Embedded offline installer -->
{{#if webview2_installer_path}}
<Binary Id="MicrosoftEdgeWebView2RuntimeInstaller.exe" SourceFile="{{webview2_installer_path}}"/>
<CustomAction Id='InvokeStandalone' BinaryKey='MicrosoftEdgeWebView2RuntimeInstaller.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
<InstallExecuteSequence>
<Custom Action='InvokeStandalone' Before='InstallFinalize'>
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
</Custom>
</InstallExecuteSequence>
{{/if}}
{{/if}}
{{#if enable_elevated_update_task}}
<!-- Install an elevated update task within Windows Task Scheduler -->
<CustomAction
Id="CreateUpdateTask"
Return="check"
Directory="INSTALLDIR"
Execute="commit"
Impersonate="yes"
ExeCommand="powershell.exe -WindowStyle hidden .\install-task.ps1" />
<InstallExecuteSequence>
<Custom Action='CreateUpdateTask' Before='InstallFinalize'>
NOT(REMOVE)
</Custom>
</InstallExecuteSequence>
<!-- Remove elevated update task during uninstall -->
<CustomAction
Id="DeleteUpdateTask"
Return="check"
Directory="INSTALLDIR"
ExeCommand="powershell.exe -WindowStyle hidden .\uninstall-task.ps1" />
<InstallExecuteSequence>
<Custom Action="DeleteUpdateTask" Before='InstallFinalize'>
(REMOVE = "ALL") AND NOT UPGRADINGPRODUCTCODE
</Custom>
</InstallExecuteSequence>
{{/if}}
<InstallExecuteSequence>
<Custom Action="LaunchApplication" After="InstallFinalize">AUTOLAUNCHAPP AND NOT Installed</Custom>
</InstallExecuteSequence>
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLDIR]" After="CostFinalize"/>
</Product>
</Wix>

View file

@ -0,0 +1,14 @@
!define NSIS_HOOK_POSTINSTALL "NSIS_HOOK_POSTINSTALL_"
!macro NSIS_HOOK_POSTINSTALL_
ExecWait '"$INSTDIR\portmaster-start.exe" install core-service --data="$INSTDIR\data"'
!macroend
!define NSIS_HOOK_PREUNINSTALL "NSIS_HOOK_PREUNINSTALL_"
!macro NSIS_HOOK_PREUNINSTALL_
ExecWait 'sc.exe stop PortmasterCore'
ExecWait 'sc.exe delete PortmasterCore'
!macroend

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<CustomAction Id="InstallPortmasterService"
Directory="INSTALLDIR"
ExeCommand="&quot;[INSTALLDIR]portmaster-start.exe&quot; install core-service --data=&quot;[INSTALLDIR]data&quot;"
Execute="commit"
Return="check"
Impersonate="no"
/>
<CustomAction Id="StopPortmasterService"
Directory="INSTALLDIR"
ExeCommand="sc.exe stop PortmasterCore"
Execute="commit"
Return="ignore"
Impersonate="no"
/>
<CustomAction Id="DeletePortmasterService"
Directory="INSTALLDIR"
ExeCommand="sc.exe delete PortmasterCore"
Execute="commit"
Return="ignore"
Impersonate="no"
/>
<InstallExecuteSequence>
<Custom Action="InstallPortmasterService" Before='InstallFinalize'>
<![CDATA[NOT Installed]]>
</Custom>
<Custom Action="StopPortmasterService" Before='InstallFinalize'>
REMOVE
</Custom>
<Custom Action="DeletePortmasterService" Before='InstallFinalize'>
REMOVE
</Custom>
</InstallExecuteSequence>
</Fragment>
</Wix>

View file

@ -33,10 +33,9 @@ PrivateDevices=yes
AmbientCapabilities=cap_chown cap_kill cap_net_admin cap_net_bind_service cap_net_broadcast cap_net_raw cap_sys_module cap_sys_ptrace cap_dac_override cap_fowner cap_fsetid AmbientCapabilities=cap_chown cap_kill cap_net_admin cap_net_bind_service cap_net_broadcast cap_net_raw cap_sys_module cap_sys_ptrace cap_dac_override cap_fowner cap_fsetid
CapabilityBoundingSet=cap_chown cap_kill cap_net_admin cap_net_bind_service cap_net_broadcast cap_net_raw cap_sys_module cap_sys_ptrace cap_dac_override cap_fowner cap_fsetid CapabilityBoundingSet=cap_chown cap_kill cap_net_admin cap_net_bind_service cap_net_broadcast cap_net_raw cap_sys_module cap_sys_ptrace cap_dac_override cap_fowner cap_fsetid
StateDirectory=portmaster StateDirectory=portmaster
ExecStartPre=-/usr/bin/portmaster-start --data $STATE_DIRECTORY clean-structure
# TODO(ppacher): add --disable-software-updates once it's merged and the release process changed. # TODO(ppacher): add --disable-software-updates once it's merged and the release process changed.
ExecStart=/usr/bin/portmaster-core --data $STATE_DIRECTORY $PORTMASTER_ARGS ExecStart=/usr/bin/portmaster-start --data /opt/safing/portmaster core -- $PORTMASTER_ARGS
ExecStartPost=-/usr/bin/portmaster-start recover-iptables ExecStopPost=-/usr/bin/portmaster-start recover-iptables
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View file

@ -57,6 +57,7 @@ func MandatoryUpdates() (identifiers []string) {
PlatformIdentifier("start/portmaster-start.exe"), PlatformIdentifier("start/portmaster-start.exe"),
PlatformIdentifier("notifier/portmaster-notifier.exe"), PlatformIdentifier("notifier/portmaster-notifier.exe"),
PlatformIdentifier("notifier/portmaster-wintoast.dll"), PlatformIdentifier("notifier/portmaster-wintoast.dll"),
PlatformIdentifier("app2/portmaster-app.zip"),
) )
} else { } else {
identifiers = append( identifiers = append(
@ -64,6 +65,7 @@ func MandatoryUpdates() (identifiers []string) {
PlatformIdentifier("core/portmaster-core"), PlatformIdentifier("core/portmaster-core"),
PlatformIdentifier("start/portmaster-start"), PlatformIdentifier("start/portmaster-start"),
PlatformIdentifier("notifier/portmaster-notifier"), PlatformIdentifier("notifier/portmaster-notifier"),
PlatformIdentifier("app2/portmaster-app"),
) )
} }
@ -88,5 +90,6 @@ func AutoUnpackUpdates() []string {
return []string{ return []string{
PlatformIdentifier("app/portmaster-app.zip"), PlatformIdentifier("app/portmaster-app.zip"),
PlatformIdentifier("app2/portmaster-app.zip"),
} }
} }