Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions BuildInstructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ on your platform please submit an issue or a pull request.
```

- You need Node.js and npm. You can find the exact versions in the `volta` section of
`desktop/package.json`. The toolchain is managed by volta.
`desktop/package.json`. The toolchain is managed by volta. This is optional if using `--daemon-only`.

- Linux

Expand All @@ -37,15 +37,15 @@ on your platform please submit an issue or a pull request.
Install the `msi` hosted here: https://github.com/volta-cli/volta

- Install Go (ideally version `1.21`) by following the [official instructions](https://golang.org/doc/install).
Newer versions may work too.
Newer versions may work too. This is optional if using `--gotatun`.

- Install a protobuf compiler (version 3.15 and up), it can be installed on most major Linux distros
via the package name `protobuf-compiler`, `protobuf` on macOS via Homebrew, and on Windows
binaries are available on their GitHub [page](https://github.com/protocolbuffers/protobuf/releases)
and they have to be put in `%PATH`. An additional package might also be required depending on
Linux distro:
- `protobuf-devel` on Fedora.
- `libprotobuf-dev` on Debian/Ubuntu.
- `libprotobuf-dev` and `protobuf-compiler` on Debian/Ubuntu.

- **`bash` must be installed and available in PATH on all platforms**. This is required for building
the desktop app:
Expand Down Expand Up @@ -75,7 +75,7 @@ sudo apt install rpm

```bash
# For building the daemon
sudo dnf install dbus-devel
sudo dnf install gcc dbus-devel
# For building the installer
sudo dnf install rpm-build
```
Expand Down Expand Up @@ -202,6 +202,11 @@ This should produce an installer exe, pkg or rpm+deb file in the `dist/` directo

Building this requires at least 1GB of memory.

## Notes on options

- `--daemon-only` - This will build daemon only Linux packages (e.g. `mullvad-vpn-daemon`). You will need to install additional build tools: `cargo install cargo-deb cargo-generate-rpm`.
- `--gotatun` - This will build with the `gotatun` Rust library instead the `wireguard-go-rs` Go library.

## Notes on targeting ARM64

### macOS
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ Line wrap the file at 100 chars. Th
* **Security**: in case of vulnerabilities.

## [Unreleased]
### Added

#### Linux
- Add `--daemon-only` build option for deb and rpm packages for CLI usage.

### Changed
- Location setting no longer defaults to Sweden, instead it uses you current location if it
has available relays, and falls back to Sweden otherwise.
Expand Down
92 changes: 80 additions & 12 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ NOTARIZE="false"
UNIVERSAL="false"
# Use gotatun instead of wireguard-go.
GOTATUN="false"
# If only the daemon should be built and packaged separately (.deb and .rpm).
DAEMON_ONLY="false"
# Enable GotaTun by default on macOS.
if [[ "$(uname -s)" == "Darwin" ]]; then
GOTATUN="true"
Expand All @@ -51,6 +53,7 @@ while [[ "$#" -gt 0 ]]; do
UNIVERSAL="true"
;;
--gotatun) GOTATUN="true";;
--daemon-only) DAEMON_ONLY="true";;
*)
log_error "Unknown parameter: $1"
exit 1
Expand Down Expand Up @@ -382,23 +385,88 @@ fi
log_info "Updating relays.json..."
cargo run -p mullvad-api --bin relay_list "${CARGO_ARGS[@]}" > build/relays.json

function build_daemon_packages {
local pkg_success=0

log_header "Installing JavaScript dependencies"
for specified_target in "${TARGETS[@]:-""}"; do
local current_target=${specified_target:-"$HOST"}
local arch="${current_target%%-*}"

pushd desktop
npm ci --no-audit --no-fund
local pkg_args=(-p mullvad-daemon)
if [[ -n "$specified_target" ]]; then
pkg_args+=(--target "$specified_target")
fi

local deb_arch="${arch}"
local rpm_arch="${arch}"

case $arch in
x86_64) deb_arch="amd64";;
aarch64) deb_arch="arm64";;
esac

local deb_name="mullvad-vpn-daemon_${PRODUCT_VERSION}_${deb_arch}.deb"
local rpm_name="mullvad-vpn-daemon_${PRODUCT_VERSION}_${rpm_arch}.rpm"
local deb_file="dist/${deb_name}"
local rpm_file="dist/${rpm_name}"

if cargo deb --help &> /dev/null ; then
log_info "Packaging Debian (*.deb) package for ${arch}..."
if cargo deb "${pkg_args[@]}" \
--deb-version "${PRODUCT_VERSION}" --no-build \
-o "${deb_file}" > /dev/null ; then
log_info "Packaged $deb_file"
pkg_success=1
fi
else
log_error "Unable to package Debian package."
log_error "Please run \"cargo install cargo-deb\" to complete."
fi

pushd packages/mullvad-vpn
if cargo generate-rpm --help &> /dev/null ; then
log_info "Packaging Fedora (*.rpm) package for ${arch}..."
if cargo generate-rpm "${pkg_args[@]}" \
-s "version = \"${PRODUCT_VERSION}\"" \
-o "${rpm_file}" ; then
log_info "Packaged $rpm_file"
pkg_success=1
fi
else
log_error "Unable to package Fedora package."
log_error "Please run \"cargo install cargo-generate-rpm\" to complete."
fi
done

log_header "Packing Mullvad VPN $PRODUCT_VERSION artifact(s)"
if [ $pkg_success -eq 0 ]; then
return 1
fi

case "$(uname -s)" in
Linux*) npm run pack:linux -- "${NPM_PACK_ARGS[@]}";;
Darwin*) npm run pack:mac -- "${NPM_PACK_ARGS[@]}";;
MINGW*) npm run pack:win -- "${NPM_PACK_ARGS[@]}";;
esac
popd
popd
return 0
}

if [[ "$DAEMON_ONLY" == "false" ]]; then

log_header "Installing JavaScript dependencies"

pushd desktop
npm ci --no-audit --no-fund

pushd packages/mullvad-vpn

log_header "Packing Mullvad VPN $PRODUCT_VERSION artifact(s)"

case "$(uname -s)" in
Linux*) npm run pack:linux -- "${NPM_PACK_ARGS[@]}";;
Darwin*) npm run pack:mac -- "${NPM_PACK_ARGS[@]}";;
MINGW*) npm run pack:win -- "${NPM_PACK_ARGS[@]}";;
esac
popd
popd
else
log_header "Packing Mullvad VPN daemon-only packages $PRODUCT_VERSION"

build_daemon_packages
fi

# When signing is enabled, we check that the working directory is clean before building,
# further up. Now verify that this is still true. The build process should never make the
Expand Down
2 changes: 1 addition & 1 deletion dist-assets/linux/after-remove.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ case $@ in
# apt remove passes "remove"
"remove")
;;
# yum remove passes a 0
# dnf remove passes a 0
"0")
remove_logs_and_cache
remove_config
Expand Down
9 changes: 9 additions & 0 deletions dist-assets/linux/daemon/postinst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -eu

chmod u+s "/usr/bin/mullvad-exclude"
ln -sf /opt/Mullvad\ VPN/resources/mullvad-problem-report /usr/bin/mullvad-problem-report

systemctl enable "/usr/lib/systemd/system/mullvad-daemon.service"
systemctl start mullvad-daemon.service || echo "Failed to start mullvad-daemon.service"
systemctl enable "/usr/lib/systemd/system/mullvad-early-boot-blocking.service"
39 changes: 39 additions & 0 deletions dist-assets/linux/daemon/postrm
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -eu

function remove_logs_and_cache {
rm -r --interactive=never /var/log/mullvad-vpn/ || \
echo "Failed to remove mullvad-vpn logs"
rm -r --interactive=never /var/cache/mullvad-vpn/ || \
echo "Failed to remove mullvad-vpn cache"
}

function remove_config {
rm -r --interactive=never /etc/mullvad-vpn || \
echo "Failed to remove mullvad-vpn config"
}

function remove_symlinks {
rm /usr/bin/mullvad-problem-report || \
echo "Failed to remove mullvad-problem-report"
}

# checking what kind of an action is taking place
case $@ in
# apt purge passes "purge"
"purge")
remove_logs_and_cache
remove_config
remove_symlinks
;;
# apt remove passes "remove"
"remove")
remove_symlinks
;;
# dnf remove passes a 0
"0")
remove_logs_and_cache
remove_config
remove_symlinks
;;
esac
16 changes: 16 additions & 0 deletions dist-assets/linux/daemon/preinst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -eu

if which systemctl &> /dev/null && systemctl is-system-running | grep -vq offline &> /dev/null; then
if systemctl status mullvad-daemon &> /dev/null; then
/opt/Mullvad\ VPN/resources/mullvad-setup prepare-restart || true
systemctl stop mullvad-daemon.service
systemctl disable mullvad-daemon.service
systemctl disable mullvad-early-boot-blocking.service || true
cp /var/log/mullvad-vpn/daemon.log /var/log/mullvad-vpn/old-install-daemon.log \
|| echo "Failed to copy old daemon log"
fi
fi

rm -f /var/cache/mullvad-vpn/relays.json
rm -f /var/cache/mullvad-vpn/api-ip-address.txt
23 changes: 23 additions & 0 deletions dist-assets/linux/daemon/prerm
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -eu

echo "Running prerm."
is_number_re='^[0-9]+$'
# Check if we're running during an upgrade step on Fedora
# https://fedoraproject.org/wiki/Packaging:Scriptlets#Syntax
if [[ "$1" =~ $is_number_re ]] && [ "$1" -gt 0 ]; then
exit 0;
fi

if [[ "$1" == "upgrade" ]]; then
exit 0;
fi

# the user might've disabled or stopped the service themselves already
systemctl stop mullvad-daemon.service || true
systemctl disable mullvad-daemon.service || true
systemctl stop mullvad-early-boot-blocking.service || true
systemctl disable mullvad-early-boot-blocking.service || true

/opt/Mullvad\ VPN/resources/mullvad-setup reset-firewall || echo "Failed to reset firewall"
/opt/Mullvad\ VPN/resources/mullvad-setup remove-device || echo "Failed to remove device from account"
55 changes: 55 additions & 0 deletions mullvad-daemon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,61 @@ LegalCopyright = "(c) 2026 Mullvad VPN AB"
InternalName = "mullvad-daemon"
OriginalFilename = "mullvad-daemon.exe"

[package.metadata.deb]
name = "mullvad-vpn-daemon"
conflicts = ["mullvad-vpn"]
maintainer = "Mullvad VPN <support@mullvadvpn.net>"
copyright = "2026, Mullvad VPN AB <support@mullvadvpn.net>"
license-file = ["../LICENSE.md", "0"]
section = "net"
priority = "optional"
depends = "$auto"
profile = "release"
maintainer-scripts = "../dist-assets/linux/daemon/"
assets = [
{ source = "target/release/mullvad", dest = "usr/bin/", mode = "755" },
{ source = "target/release/mullvad-daemon", dest = "usr/bin/", mode = "755" },
{ source = "target/release/mullvad-exclude", dest = "usr/bin/", mode = "755" },
{ source = "target/release/mullvad-setup", dest = "opt/Mullvad VPN/resources/", mode = "755" },
{ source = "target/release/mullvad-problem-report", dest = "opt/Mullvad VPN/resources/", mode = "755" },
{ source = "../dist-assets/linux/mullvad-daemon.service", dest = "usr/lib/systemd/system/", mode = "644" },
{ source = "../dist-assets/linux/mullvad-early-boot-blocking.service", dest = "usr/lib/systemd/system/", mode = "644" },
{ source = "../dist-assets/ca.crt", dest = "opt/Mullvad VPN/resources/", mode = "644" },
{ source = "../build/relays.json", dest = "opt/Mullvad VPN/resources/", mode = "644" },
{ source = "../build/shell-completions/mullvad.bash", dest = "usr/share/bash-completion/completions/mullvad", mode = "644" },
{ source = "../build/shell-completions/_mullvad", dest = "usr/local/share/zsh/site-functions/_mullvad", mode = "644" },
{ source = "../build/shell-completions/mullvad.fish", dest = "usr/share/fish/vendor_completions.d/mullvad.fish", mode = "644" },
{ source = "../CHANGELOG.md", dest = "opt/Mullvad VPN/resources/", mode = "644" },
]

[package.metadata.generate-rpm]
name = "mullvad-vpn-daemon"
vendor = "Mullvad VPN <support@mullvadvpn.net>"
auto-req = "yes"
pre_install_script = "../dist-assets/linux/daemon/preinst"
pre_uninstall_script = "../dist-assets/linux/daemon/prerm"
post_install_script = "../dist-assets/linux/daemon/postinst"
post_uninstall_script = "../dist-assets/linux/daemon/postrm"
post_trans_script = "../dist-assets/linux/post-transaction.sh"
assets = [
{ source = "target/release/mullvad", dest = "/usr/bin/", mode = "755" },
{ source = "target/release/mullvad-daemon", dest = "/usr/bin/", mode = "755" },
{ source = "target/release/mullvad-exclude", dest = "/usr/bin/", mode = "755" },
{ source = "target/release/mullvad-setup", dest = "/opt/Mullvad VPN/resources/", mode = "755" },
{ source = "target/release/mullvad-problem-report", dest = "/opt/Mullvad VPN/resources/", mode = "755" },
{ source = "../dist-assets/linux/mullvad-daemon.service", dest = "/usr/lib/systemd/system/", mode = "644" },
{ source = "../dist-assets/linux/mullvad-early-boot-blocking.service", dest = "/usr/lib/systemd/system/", mode = "644" },
{ source = "../dist-assets/ca.crt", dest = "/opt/Mullvad VPN/resources/", mode = "644" },
{ source = "../build/relays.json", dest = "/opt/Mullvad VPN/resources/", mode = "644" },
{ source = "../build/shell-completions/mullvad.bash", dest = "/usr/share/bash-completion/completions/mullvad", mode = "644" },
{ source = "../build/shell-completions/_mullvad", dest = "/usr/share/zsh/site-functions/_mullvad", mode = "644" },
{ source = "../build/shell-completions/mullvad.fish", dest = "/usr/share/fish/vendor_completions.d/mullvad.fish", mode = "644" },
{ source = "../CHANGELOG.md", dest = "/opt/Mullvad VPN/resources/", mode = "644" },
]

[package.metadata.generate-rpm.conflicts]
mullvad-vpn = "*"

[dependencies]
anyhow = { workspace = true }
chrono = { workspace = true }
Expand Down
Loading