feat(gui-client): rename IPC service to Tunnel service (#9154)

The name IPC service is not very descriptive. By nature of being
separate processes, we need to use IPC to communicate between them. The
important thing is that the service process has control over the tunnel.
Therefore, we rename everything to "Tunnel service".

The only part that is not changed are historic changelog entries.

Resolves: #9048
This commit is contained in:
Thomas Eizinger
2025-05-19 19:52:06 +10:00
committed by GitHub
parent e14c4e1eb8
commit 1bdba3601a
44 changed files with 232 additions and 154 deletions

View File

@@ -17,7 +17,7 @@ pub struct DeviceId {
/// e.g. `C:\ProgramData\dev.firezone.client/firezone-id.json` or
/// `/var/lib/dev.firezone.client/config/firezone-id.json`.
pub(crate) fn path() -> Result<PathBuf> {
let path = crate::known_dirs::ipc_service_config()
let path = crate::known_dirs::tunnel_service_config()
.context("Failed to compute path for firezone-id file")?
.join("firezone-id.json");
Ok(path)
@@ -53,7 +53,7 @@ pub fn get_or_create() -> Result<DeviceId> {
fs::create_dir_all(dir).context("Failed to create dir for firezone-id")?;
set_dir_permissions(dir).with_context(|| {
format!(
"Couldn't set permissions on IPC service config dir `{}`",
"Couldn't set permissions on Tunnel service config dir `{}`",
dir.display()
)
})?;

View File

@@ -50,7 +50,7 @@ impl DnsController {
}
}
// TODO: Move DNS and network change listening to the IPC service, so this won't
// TODO: Move DNS and network change listening to the Tunnel service, so this won't
// need to be public.
pub fn system_resolvers_for_gui() -> Result<Vec<IpAddr>> {
system_resolvers(DnsControlMethod::default())

View File

@@ -10,7 +10,7 @@
use anyhow::{Context as _, Result};
use std::path::PathBuf;
pub use platform::{ipc_service_config, ipc_service_logs, logs, runtime, session, settings};
pub use platform::{logs, runtime, session, settings, tunnel_service_config, tunnel_service_logs};
#[cfg(target_os = "linux")]
#[path = "known_dirs/linux.rs"]
@@ -24,9 +24,9 @@ pub mod platform;
#[path = "known_dirs/windows.rs"]
pub mod platform;
pub fn ipc_log_filter() -> Result<PathBuf> {
Ok(ipc_service_config()
.context("Failed to compute `ipc_service_config` directory")?
pub fn tunnel_log_filter() -> Result<PathBuf> {
Ok(tunnel_service_config()
.context("Failed to compute `tunnel_service_config` directory")?
.join("log-filter"))
}
@@ -37,8 +37,8 @@ mod tests {
#[test]
fn smoke() {
for dir in [
ipc_service_config(),
ipc_service_logs(),
tunnel_service_config(),
tunnel_service_logs(),
logs(),
runtime(),
session(),

View File

@@ -1,7 +1,7 @@
use crate::BUNDLE_ID;
use std::path::PathBuf;
/// Path for IPC service config that the IPC service can write
/// Path for Tunnel service config that the Tunnel service can write
///
/// All writes should use `atomicwrites`.
///
@@ -15,12 +15,12 @@ use std::path::PathBuf;
///
/// `config` to match how Windows has `config` and `data` both under `AppData/Local/$BUNDLE_ID`
#[expect(clippy::unnecessary_wraps)] // Signature must match Windows
pub fn ipc_service_config() -> Option<PathBuf> {
pub fn tunnel_service_config() -> Option<PathBuf> {
Some(PathBuf::from("/var/lib").join(BUNDLE_ID).join("config"))
}
#[expect(clippy::unnecessary_wraps)] // Signature must match Windows
pub fn ipc_service_logs() -> Option<PathBuf> {
pub fn tunnel_service_logs() -> Option<PathBuf> {
// TODO: This is magic, it must match the systemd file
Some(PathBuf::from("/var/log").join(BUNDLE_ID))
}

View File

@@ -1,10 +1,10 @@
use std::path::PathBuf;
pub fn ipc_service_config() -> Option<PathBuf> {
pub fn tunnel_service_config() -> Option<PathBuf> {
unimplemented!()
}
pub fn ipc_service_logs() -> Option<PathBuf> {
pub fn tunnel_service_logs() -> Option<PathBuf> {
unimplemented!()
}

View File

@@ -15,12 +15,12 @@ pub fn app_local_data_dir() -> Result<PathBuf> {
Ok(path)
}
/// Path for IPC service config that the IPC service can write
/// Path for Tunnel service config that the Tunnel service can write
///
/// All writes should use `atomicwrites`.
///
/// On Windows, `C:/ProgramData/$BUNDLE_ID/config`
pub fn ipc_service_config() -> Option<PathBuf> {
pub fn tunnel_service_config() -> Option<PathBuf> {
Some(
get_known_folder_path(KnownFolder::ProgramData)?
.join(BUNDLE_ID)
@@ -28,7 +28,7 @@ pub fn ipc_service_config() -> Option<PathBuf> {
)
}
pub fn ipc_service_logs() -> Option<PathBuf> {
pub fn tunnel_service_logs() -> Option<PathBuf> {
Some(
get_known_folder_path(KnownFolder::ProgramData)?
.join(BUNDLE_ID)

View File

@@ -122,7 +122,7 @@ where
}
}
/// Messages that connlib can produce and send to the headless Client, IPC service, or GUI process.
/// Messages that connlib can produce and send to the headless Client, Tunnel service, or GUI process.
///
/// i.e. callbacks
// The names are CamelCase versions of the connlib callbacks.

View File

@@ -57,9 +57,9 @@ rm "$INTERMEDIATE_DIR"/*.tar.gz
# │   ├── lib
# │   │   ├── systemd
# │   │   │   └── system
# │   │   │   └── firezone-client-ipc.service
# │   │   │   └── firezone-client-tunnel.service
# │   │   └── sysusers.d
# │   │   └── firezone-client-ipc.conf
# │   │   └── firezone-client-tunnel.conf
# │   └── share
# │   ├── applications
# │   │   └── firezone-client-gui.desktop
@@ -68,15 +68,15 @@ rm "$INTERMEDIATE_DIR"/*.tar.gz
# └── debian-binary
# Add the scripts
cp src-tauri/deb_files/postinst src-tauri/deb_files/prerm "$INTERMEDIATE_DIR/control/"
cp src-tauri/deb_files/postinst src-tauri/deb_files/prerm src-tauri/deb_files/preinst "$INTERMEDIATE_DIR/control/"
# Add the IPC service
cp ../target/release/firezone-client-ipc "$INTERMEDIATE_DIR/data/usr/bin/"
# Add the Tunnel service
cp ../target/release/firezone-client-tunnel "$INTERMEDIATE_DIR/data/usr/bin/"
pushd "$INTERMEDIATE_DIR"
# Rebuild the control tarball
tar -C "control" -czf "control.tar.gz" control md5sums postinst prerm
tar -C "control" -czf "control.tar.gz" control md5sums preinst postinst prerm
# Rebuild the data tarball
tar -C "data" -czf "data.tar.gz" usr

View File

@@ -36,15 +36,15 @@ If the client stops running while signed in, then the token may be stored in Win
## Linux Permissions
- [ ] The IPC service with `run-debug` can NOT run as a normal user
- [ ] The IPC service with `run-debug` can run with `sudo`
- [ ] The Tunnel service with `run-debug` can NOT run as a normal user
- [ ] The Tunnel service with `run-debug` can run with `sudo`
- [ ] The GUI can run as a normal user
- [ ] The GUI can NOT run with `sudo`
## Windows Permissions
- [ ] The IPC service with `run-debug` can NOT run as a normal user
- [ ] The IPC service with `run-debug` can run as admin
- [ ] The Tunnel service with `run-debug` can NOT run as a normal user
- [ ] The Tunnel service with `run-debug` can run as admin
- [ ] The GUI can run as a normal user
- [ ] The GUI can run as admin

View File

@@ -1,5 +1,5 @@
[Unit]
Description=Firezone Client
Description=Firezone Client Tunnel Service
After=systemd-resolved.service
Wants=systemd-resolved.service
@@ -40,9 +40,9 @@ SystemCallFilter=@aio @basic-io @file-system @io-event @ipc @network-io @signal
UMask=077
Environment="LOG_DIR=/var/log/dev.firezone.client"
EnvironmentFile=-/etc/default/firezone-client-ipc
EnvironmentFile=-/etc/default/firezone-client-tunnel
ExecStart=firezone-client-ipc run
ExecStart=firezone-client-tunnel run
Type=notify
# Unfortunately we need root to control DNS
User=root

View File

@@ -4,12 +4,12 @@
set -euo pipefail
SERVICE_NAME="firezone-client-ipc"
SERVICE_NAME="firezone-client-tunnel"
# Creates the system group `firezone-client`
sudo systemd-sysusers
echo "Starting and enabling Firezone IPC service..."
echo "Starting and enabling Firezone Tunnel service..."
sudo systemctl daemon-reload
sudo systemctl enable "$SERVICE_NAME"
sudo systemctl restart "$SERVICE_NAME"

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Usage: dpkg will call this after installing our files
# This must be idempotent
set -euo pipefail
OLD_SERVICE_NAME="firezone-client-ipc"
sudo systemctl disable "$OLD_SERVICE_NAME" >/dev/null 2>&1 || true
sudo systemctl stop "$OLD_SERVICE_NAME" >/dev/null 2>&1 || true

View File

@@ -3,7 +3,7 @@
set -euo pipefail
SERVICE_NAME="firezone-client-ipc"
SERVICE_NAME="firezone-client-tunnel"
sudo systemctl disable "$SERVICE_NAME"
sudo systemctl stop "$SERVICE_NAME"

View File

@@ -16,6 +16,11 @@ BuildRequires: systemd-rpm-macros
%prep
%pre
# Stop and disable the old service. The service may no longer exist so we ensure this never fails.
systemctl disable "$OLD_SERVICE_NAME" >/dev/null 2>&1 || true
systemctl stop "$OLD_SERVICE_NAME" >/dev/null 2>&1 || true
%build
%install
@@ -25,7 +30,7 @@ mkdir -p \
BINS="%{_topdir}/../../target/release"
cp "$BINS/firezone-client-ipc" "%{buildroot}/usr/bin/"
cp "$BINS/firezone-client-tunnel" "%{buildroot}/usr/bin/"
cp "$BINS/firezone-client-gui" "%{buildroot}/usr/lib/dev.firezone.client/"
cp "%{_topdir}/../src-tauri/rpm_files/gui-shim.sh" "%{buildroot}/usr/bin/firezone-client-gui"
@@ -103,12 +108,12 @@ mkdir -p \
"$ICONS/512x512/apps"
cp \
"%{_topdir}/../src-tauri/deb_files/firezone-client-ipc.service" \
"%{_topdir}/../src-tauri/deb_files/firezone-client-tunnel.service" \
"%{buildroot}/usr/lib/systemd/system/"
cp \
"%{_topdir}/../src-tauri/deb_files/sysusers.conf" \
"%{buildroot}/usr/lib/sysusers.d/firezone-client-ipc.conf"
"%{buildroot}/usr/lib/sysusers.d/firezone-client-tunnel.conf"
cp \
"%{_topdir}/../src-tauri/rpm_files/firezone-client-gui.desktop" \
@@ -127,7 +132,7 @@ cp \
"$ICONS/512x512/apps/firezone-client-gui.png"
%files
/usr/bin/firezone-client-ipc
/usr/bin/firezone-client-tunnel
/usr/bin/firezone-client-gui
/usr/lib/dev.firezone.client/firezone-client-gui
@@ -163,8 +168,8 @@ cp \
/usr/lib/dev.firezone.client/libX11.so.6
/usr/lib/dev.firezone.client/libX11-xcb.so.1
/usr/lib/systemd/system/firezone-client-ipc.service
/usr/lib/sysusers.d/firezone-client-ipc.conf
/usr/lib/systemd/system/firezone-client-tunnel.service
/usr/lib/sysusers.d/firezone-client-tunnel.conf
/usr/share/applications/firezone-client-gui.desktop
/usr/share/icons/hicolor/32x32/apps/firezone-client-gui.png
@@ -188,10 +193,10 @@ cp \
%endif
%post
%systemd_post firezone-client-ipc.service
%systemd_post firezone-client-tunnel.service
%preun
%systemd_preun firezone-client-ipc.service
%systemd_preun firezone-client-tunnel.service
%postun
%systemd_postun_with_restart firezone-client-ipc.service
%systemd_postun_with_restart firezone-client-tunnel.service

View File

@@ -60,7 +60,7 @@ pub struct Cli {
#[derive(clap::Subcommand)]
enum Cmd {
/// Needed to test the IPC service on aarch64 Windows,
/// Needed to test the Tunnel service on aarch64 Windows,
/// where the Tauri MSI bundler doesn't work yet
Install,
Run,
@@ -80,7 +80,7 @@ mod tests {
use clap::Parser;
use std::path::PathBuf;
const EXE_NAME: &str = "firezone-client-ipc";
const EXE_NAME: &str = "firezone-client-tunnel";
// Can't remember how Clap works sometimes
// Also these are examples

View File

@@ -38,7 +38,7 @@ fn main() -> ExitCode {
);
// Get the device ID before starting Tokio, so that all the worker threads will inherit the correct scope.
// Technically this means we can fail to get the device ID on a newly-installed system, since the IPC service may not have fully started up when the GUI process reaches this point, but in practice it's unlikely.
// Technically this means we can fail to get the device ID on a newly-installed system, since the Tunnel service may not have fully started up when the GUI process reaches this point, but in practice it's unlikely.
if let Ok(id) = firezone_bin_shared::device_id::get() {
Telemetry::set_firezone_id(id.id);
}
@@ -159,7 +159,9 @@ fn try_main(cli: Cli, rt: &tokio::runtime::Runtime, mut settings: AdvancedSettin
.root_cause()
.is::<firezone_gui_client::ipc::NotFound>()
{
show_error_dialog("Couldn't find Firezone IPC service. Is the service running?")?;
show_error_dialog(
"Couldn't find Firezone Tunnel service. Is the service running?",
)?;
return Err(anyhow);
}

View File

@@ -7,7 +7,7 @@ pub async fn clear_logs(path: &Path) -> Result<()> {
Ok(x) => x,
Err(error) => {
if matches!(error.kind(), NotFound) {
// In smoke tests, the IPC service runs in debug mode, so it won't write any logs to disk. If the IPC service's log dir doesn't exist, we shouldn't crash, it's correct to simply not delete the non-existent files
// In smoke tests, the Tunnel service runs in debug mode, so it won't write any logs to disk. If the Tunnel service's log dir doesn't exist, we shouldn't crash, it's correct to simply not delete the non-existent files
return Ok(());
}
// But any other error like permissions errors, should bubble.

View File

@@ -86,7 +86,7 @@ pub trait GuiIntegration {
pub enum ControllerRequest {
/// The GUI wants us to use these settings in-memory, they've already been saved to disk
ApplySettings(Box<AdvancedSettings>),
/// Clear the GUI's logs and await the IPC service to clear its logs
/// Clear the GUI's logs and await the Tunnel service to clear its logs
ClearLogs(oneshot::Sender<Result<(), String>>),
/// The same as the arguments to `client::logging::export_logs_to`
ExportLogs {
@@ -654,12 +654,12 @@ impl<I: GuiIntegration> Controller<I> {
self.update_disabled_resources().await?;
}
service::ServerMsg::TerminatingGracefully => {
tracing::info!("IPC service exited gracefully");
tracing::info!("Tunnel service exited gracefully");
self.integration
.set_tray_icon(system_tray::icon_terminating());
self.integration.show_notification(
"Firezone disconnected",
"The Firezone IPC service was shutdown, quitting GUI process.",
"The Firezone Tunnel service was shutdown, quitting GUI process.",
)?;
return Ok(ControlFlow::Break(()));

View File

@@ -1,6 +1,6 @@
//! A module for registering, catching, and parsing deep links that are sent over to the app's already-running instance
// The IPC parts use the same primitives as the IPC service, UDS on Linux
// The IPC parts use the same primitives as the Tunnel service, UDS on Linux
// and named pipes on Windows, so TODO de-dupe the IPC code
use crate::{

View File

@@ -8,7 +8,7 @@ mod platform {
/// Returns true if all permissions are correct for the GUI to run
///
/// Everything that needs root / admin powers happens in the IPC services,
/// Everything that needs root / admin powers happens in the Tunnel services,
/// so for security and practicality reasons the GUIs must be non-root.
/// (In Linux by default a root GUI app barely works at all)
pub fn gui_check() -> Result<bool, Error> {

View File

@@ -364,7 +364,7 @@ pub struct Icon {
pub update_ready: bool,
}
/// Generic icon for unusual terminating cases like if the IPC service stops running
/// Generic icon for unusual terminating cases like if the Tunnel service stops running
pub(crate) fn icon_terminating() -> Icon {
Icon {
base: IconBase::SignedOut,

View File

@@ -29,7 +29,7 @@ pub(crate) mod platform;
pub(crate) mod platform;
#[derive(Debug, thiserror::Error)]
#[error("Couldn't find IPC service `{0}`")]
#[error("Couldn't find IPC socket `{0}`")]
pub struct NotFound(String);
/// A name that both the server and client can use to find each other
@@ -304,7 +304,7 @@ mod tests {
/// Replicate #5143
///
/// When the IPC service has disconnected from a GUI and loops over, sometimes
/// When the Tunnel service has disconnected from a GUI and loops over, sometimes
/// the named pipe is not ready. If our IPC code doesn't handle this right,
/// this test will fail.
#[tokio::test]

View File

@@ -27,7 +27,7 @@ pub type ClientStream = UnixStream;
/// On Windows `ClientStream` and `ServerStream` differ
pub(crate) type ServerStream = UnixStream;
/// Connect to the IPC service
/// Connect to the Tunnel service
#[expect(clippy::wildcard_enum_match_arm)]
pub async fn connect_to_socket(id: SocketId) -> Result<ClientStream> {
let path = ipc_path(id);
@@ -70,7 +70,7 @@ impl Server {
std::fs::set_permissions(&sock_path, perms).context("Failed to set permissions on UDS")?;
// TODO: Change this to `notify_service_controller` and put it in
// the same place in the IPC service's main loop as in the Headless Client.
// the same place in the Tunnel service's main loop as in the Headless Client.
sd_notify::notify(true, &[sd_notify::NotifyState::Ready])?;
Ok(Self { listener, id })
}

View File

@@ -54,7 +54,7 @@ impl Server {
// `&mut self` needed to match the Linux signature
pub(crate) async fn next_client(&mut self) -> Result<ServerStream> {
// Fixes #5143. In the IPC service, if we close the pipe and immediately re-open
// Fixes #5143. In the Tunnel service, if we close the pipe and immediately re-open
// it, Tokio may not get a chance to clean up the pipe. Yielding seems to fix
// this in tests, but `yield_now` doesn't make any such guarantees, so
// we also do a loop.

View File

@@ -21,6 +21,6 @@ pub use clear_logs::clear_logs;
/// The Sentry "release" we are part of.
///
/// IPC service and GUI client are always bundled into a single release.
/// Hence, we have a single constant for IPC service and GUI client.
/// Tunnel service and GUI client are always bundled into a single release.
/// Hence, we have a single constant for Tunnel service and GUI client.
pub const RELEASE: &str = concat!("gui-client@", env!("CARGO_PKG_VERSION"));

View File

@@ -169,19 +169,22 @@ pub fn setup_gui(directives: &str) -> Result<Handles> {
})
}
/// Starts logging for the production IPC service
/// Starts logging for the production Tunnel service
///
/// Returns: A `Handle` that must be kept alive. Dropping it stops logging
/// and flushes the log file.
pub fn setup_ipc(
pub fn setup_tunnel(
log_path: Option<PathBuf>,
) -> Result<(
firezone_logging::file::Handle,
firezone_logging::FilterReloadHandle,
)> {
// If `log_dir` is Some, use that. Else call `ipc_service_logs`
// If `log_dir` is Some, use that. Else call `tunnel_service_logs`
let log_path = log_path.map_or_else(
|| known_dirs::ipc_service_logs().context("Should be able to compute IPC service logs dir"),
|| {
known_dirs::tunnel_service_logs()
.context("Should be able to compute Tunnel service logs dir")
},
Ok,
)?;
std::fs::create_dir_all(&log_path)
@@ -211,7 +214,7 @@ pub fn setup_ipc(
?directives,
system_uptime = firezone_bin_shared::uptime::get().map(tracing::field::debug),
log_path = %log_path.display(),
"`ipc-service` started logging"
"`tunnel service` started logging"
);
Ok((file_handle, file_reloader.merge(stdout_reloader)))
@@ -229,13 +232,13 @@ pub fn setup_stdout() -> Result<FilterReloadHandle> {
Ok(reloader)
}
/// Reads the log filter for the IPC service or for debug commands
/// Reads the log filter for the Tunnel service or for debug commands
///
/// e.g. `info`
///
/// Reads from:
/// 1. `RUST_LOG` env var
/// 2. `known_dirs::ipc_log_filter()` file
/// 2. `known_dirs::tunnel_log_filter()` file
/// 3. Hard-coded default `SERVICE_RUST_LOG`
///
/// Errors if something is badly wrong, e.g. the directory for the config file
@@ -250,8 +253,9 @@ pub(crate) fn get_log_filter() -> Result<String> {
return Ok(filter);
}
if let Ok(filter) = std::fs::read_to_string(firezone_bin_shared::known_dirs::ipc_log_filter()?)
.map(|s| s.trim().to_string())
if let Ok(filter) =
std::fs::read_to_string(firezone_bin_shared::known_dirs::tunnel_log_filter()?)
.map(|s| s.trim().to_string())
{
return Ok(filter);
}
@@ -332,7 +336,7 @@ fn add_dir_to_zip(
Ok(x) => x,
Err(error) => {
if matches!(error.kind(), NotFound) {
// In smoke tests, the IPC service runs in debug mode, so it won't write any logs to disk. If the IPC service's log dir doesn't exist, we shouldn't crash, it's correct to simply not add any files to the zip
// In smoke tests, the Tunnel service runs in debug mode, so it won't write any logs to disk. If the Tunnel service's log dir doesn't exist, we shouldn't crash, it's correct to simply not add any files to the zip
return Ok(());
}
// But any other error like permissions errors, should bubble.
@@ -384,7 +388,8 @@ async fn count_one_dir(path: &Path) -> Result<FileCount> {
fn log_paths() -> Result<Vec<LogPath>> {
Ok(vec![
LogPath {
src: known_dirs::ipc_service_logs().context("Can't compute IPC service logs dir")?,
src: known_dirs::tunnel_service_logs()
.context("Can't compute Tunnel service logs dir")?,
dst: PathBuf::from("connlib"),
},
LogPath {

View File

@@ -64,11 +64,11 @@ pub enum ClientMsg {
},
}
/// Messages that end up in the GUI, either forwarded from connlib or from the IPC service.
/// Messages that end up in the GUI, either forwarded from connlib or from the Tunnel service.
#[derive(Debug, serde::Deserialize, serde::Serialize, strum::Display)]
pub enum ServerMsg {
Hello,
/// The IPC service finished clearing its log dir.
/// The Tunnel service finished clearing its log dir.
ClearedLogs(Result<(), String>),
ConnectResult(Result<(), ConnectError>),
DisconnectedGracefully,
@@ -77,7 +77,7 @@ pub enum ServerMsg {
is_authentication_error: bool,
},
OnUpdateResources(Vec<ResourceView>),
/// The IPC service is terminating, maybe due to a software update
/// The Tunnel service is terminating, maybe due to a software update
///
/// This is a hint that the Client should exit with a message like,
/// "Firezone is updating, please restart the GUI" instead of an error like,
@@ -108,7 +108,7 @@ impl From<anyhow::Error> for ConnectError {
}
}
/// Run the IPC service and terminate gracefully if we catch a terminate signal
/// Run the Tunnel service and terminate gracefully if we catch a terminate signal
///
/// If an IPC client is connected when we catch a terminate signal, we send the
/// client a hint about that before we exit.
@@ -118,7 +118,7 @@ async fn ipc_listen(
signals: &mut signals::Terminate,
telemetry: &mut Telemetry,
) -> Result<()> {
// Create the device ID and IPC service config dir if needed
// Create the device ID and Tunnel service config dir if needed
// This also gives the GUI a safe place to put the log filter config
let firezone_id = device_id::get_or_create()
.context("Failed to read / create device ID")?
@@ -238,7 +238,7 @@ impl<'a> Handler<'a> {
/// Run the event loop to communicate with an IPC client.
///
/// If the IPC service needs to terminate, we catch that from `signals` and send
/// If the Tunnel service needs to terminate, we catch that from `signals` and send
/// the client a hint to shut itself down gracefully.
///
/// The return type is infallible so that we only give up on an IPC client explicitly
@@ -360,7 +360,7 @@ impl<'a> Handler<'a> {
match msg {
ClientMsg::ClearLogs => {
let result = crate::clear_logs(
&firezone_bin_shared::known_dirs::ipc_service_logs()
&firezone_bin_shared::known_dirs::tunnel_service_logs()
.context("Can't compute logs dir")?,
)
.await;
@@ -385,7 +385,7 @@ impl<'a> Handler<'a> {
ClientMsg::ApplyLogFilter { directives } => {
self.log_filter_reloader.reload(&directives)?;
let path = known_dirs::ipc_log_filter()?;
let path = known_dirs::tunnel_log_filter()?;
if let Err(e) = AtomicFile::new(&path, OverwriteBehavior::AllowOverwrite)
.write(|f| f.write_all(directives.as_bytes()))
@@ -531,7 +531,7 @@ pub fn run_debug(dns_control: DnsControlMethod) -> Result<()> {
system_uptime_seconds = firezone_bin_shared::uptime::get().map(|dur| dur.as_secs()),
);
if !elevation_check()? {
bail!("IPC service failed its elevation check, try running as admin / root");
bail!("Tunnel service failed its elevation check, try running as admin / root");
}
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
@@ -548,7 +548,7 @@ pub fn run_debug(dns_control: DnsControlMethod) -> Result<()> {
))
.inspect(|_| rt.block_on(telemetry.stop()))
.inspect_err(|e| {
tracing::error!("IPC service failed: {e:#}");
tracing::error!("Tunnel service failed: {e:#}");
rt.block_on(telemetry.stop_on_crash())
})
@@ -565,7 +565,7 @@ pub fn run_smoke_test() -> Result<()> {
let log_filter_reloader = crate::logging::setup_stdout()?;
if !elevation_check()? {
bail!("IPC service failed its elevation check, try running as admin / root");
bail!("Tunnel service failed its elevation check, try running as admin / root");
}
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
@@ -574,7 +574,7 @@ pub fn run_smoke_test() -> Result<()> {
let mut dns_controller = DnsController {
dns_control_method: Default::default(),
};
// Deactivate Firezone DNS control in case the system or IPC service crashed
// Deactivate Firezone DNS control in case the system or Tunnel service crashed
// and we need to recover. <https://github.com/firezone/firezone/issues/4899>
dns_controller.deactivate()?;
let mut signals = signals::Terminate::new()?;

View File

@@ -9,9 +9,9 @@ use firezone_telemetry::Telemetry;
///
/// Linux uses the CLI args from here, Windows does not
pub fn run(log_dir: Option<PathBuf>, dns_control: DnsControlMethod) -> Result<()> {
let (_handle, log_filter_reloader) = crate::logging::setup_ipc(log_dir)?;
let (_handle, log_filter_reloader) = crate::logging::setup_tunnel(log_dir)?;
if !elevation_check()? {
bail!("IPC service failed its elevation check, try running as admin / root");
bail!("Tunnel service failed its elevation check, try running as admin / root");
}
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
@@ -28,13 +28,13 @@ pub fn run(log_dir: Option<PathBuf>, dns_control: DnsControlMethod) -> Result<()
))
.inspect(|_| rt.block_on(telemetry.stop()))
.inspect_err(|e| {
tracing::error!("IPC service failed: {e:#}");
tracing::error!("Tunnel service failed: {e:#}");
rt.block_on(telemetry.stop_on_crash())
})
}
/// Returns true if the IPC service can run properly
/// Returns true if the Tunnel service can run properly
// Fallible on Windows
#[expect(clippy::unnecessary_wraps)]
pub fn elevation_check() -> Result<bool> {

View File

@@ -4,7 +4,7 @@ use std::path::PathBuf;
pub fn run(log_dir: Option<PathBuf>, _dns_control: DnsControlMethod) -> Result<()> {
// We call this here to avoid a dead-code warning.
let (_handle, _log_filter_reloader) = crate::logging::setup_ipc(log_dir)?;
let (_handle, _log_filter_reloader) = crate::logging::setup_tunnel(log_dir)?;
bail!("not implemented")
}

View File

@@ -32,7 +32,7 @@ use windows_service::{
const SERVICE_NAME: &str = "firezone_client_ipc";
const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
/// Returns true if the IPC service can run properly
/// Returns true if the Tunnel service can run properly
pub fn elevation_check() -> Result<bool> {
let token = ProcessToken::our_process().context("Failed to get process token")?;
let elevated = token
@@ -152,10 +152,10 @@ pub fn install() -> Result<()> {
let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE;
let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?;
let name = "FirezoneClientIpcServiceDebug";
let name = "FirezoneClientTunnelServiceDebug";
// Un-install existing one first if needed
if let Err(e) = uninstall_ipc_service(&service_manager, name)
if let Err(e) = uninstall_tunnel_service(&service_manager, name)
.with_context(|| format!("Failed to uninstall `{name}`"))
{
tracing::debug!("{e:#}");
@@ -164,7 +164,7 @@ pub fn install() -> Result<()> {
let executable_path = std::env::current_exe()?;
let service_info = ServiceInfo {
name: OsString::from(name),
display_name: OsString::from("Firezone Client IPC (Debug)"),
display_name: OsString::from("Firezone Tunnel Service (Debug)"),
service_type: ServiceType::OWN_PROCESS,
start_type: ServiceStartType::AutoStart,
error_control: ServiceErrorControl::Normal,
@@ -179,7 +179,10 @@ pub fn install() -> Result<()> {
Ok(())
}
fn uninstall_ipc_service(service_manager: &ServiceManager, name: impl AsRef<OsStr>) -> Result<()> {
fn uninstall_tunnel_service(
service_manager: &ServiceManager,
name: impl AsRef<OsStr>,
) -> Result<()> {
let service_access = ServiceAccess::DELETE;
let service = service_manager.open_service(name, service_access)?;
service.delete()?;
@@ -201,7 +204,7 @@ fn service_run(arguments: Vec<OsString>) {
// `arguments` doesn't seem to work right when running as a Windows service
// (even though it's meant for that) so just use the default log dir.
let (handle, log_filter_reloader) =
crate::logging::setup_ipc(None).expect("Should be able to set up logging");
crate::logging::setup_tunnel(None).expect("Should be able to set up logging");
if let Err(error) = fallible_service_run(arguments, handle, log_filter_reloader) {
tracing::error!("`fallible_windows_service_run` returned an error: {error:#}");
}
@@ -219,7 +222,7 @@ fn fallible_service_run(
) -> Result<()> {
tracing::info!(?arguments, "fallible_windows_service_run");
if !elevation_check()? {
bail!("IPC service failed its elevation check, try running as admin / root");
bail!("Tunnel service failed its elevation check, try running as admin / root");
}
let rt = tokio::runtime::Builder::new_current_thread()
@@ -289,7 +292,7 @@ fn fallible_service_run(
))
.inspect(|_| rt.block_on(telemetry.stop()))
.inspect_err(|e| {
tracing::error!("IPC service failed: {e:#}");
tracing::error!("Tunnel service failed: {e:#}");
rt.block_on(telemetry.stop_on_crash())
});

View File

@@ -10,8 +10,8 @@
"linux": {
"deb": {
"files": {
"/usr/lib/systemd/system/firezone-client-ipc.service": "./deb_files/firezone-client-ipc.service",
"/usr/lib/sysusers.d/firezone-client-ipc.conf": "./deb_files/sysusers.conf"
"/usr/lib/systemd/system/firezone-client-tunnel.service": "./deb_files/firezone-client-tunnel.service",
"/usr/lib/sysusers.d/firezone-client-tunnel.conf": "./deb_files/sysusers.conf"
},
"desktopTemplate": "./deb_files/firezone-client-gui.desktop"
}
@@ -20,7 +20,7 @@
"windows": {
"wix": {
"bannerPath": "./win_files/banner.png",
"componentRefs": ["FirezoneClientIpcService"],
"componentRefs": ["RemoveOldFirezoneService", "FirezoneClientTunnelService"],
"dialogImagePath": "./win_files/install_dialog.png",
"fragmentPaths": ["./win_files/service.wxs"],
"template": "./win_files/main.wxs"

View File

@@ -1,6 +1,6 @@
{
"build": {
"beforeBundleCommand": "bash -c '../../scripts/build/sign.sh ../target/release/Firezone.exe ../target/release/firezone-client-ipc.exe'"
"beforeBundleCommand": "bash -c '../../scripts/build/sign.sh ../target/release/Firezone.exe ../target/release/firezone-client-tunnel.exe'"
},
"mainBinaryName": "Firezone",
"productName": "Firezone"

View File

@@ -345,6 +345,17 @@ component.
</InstallExecuteSequence>
{{/if}}
<!-- Hack for removing duplicate service binary. Can be removed in later versions when most/all users have upgraded to a newer version of Firezone. -->
<CustomAction Id="DeleteOldServiceFile"
Directory="INSTALLDIR"
Execute="deferred"
Return="ignore"
ExeCommand="powershell.exe -NoProfile -WindowStyle Hidden -Command &quot;Remove-Item -Path '[INSTALLDIR]firezone-client-ipc.exe' -Force -ErrorAction SilentlyContinue&quot;" />
<InstallExecuteSequence>
<Custom Action="DeleteOldServiceFile" Before="InstallFinalize">NOT Installed</Custom>
</InstallExecuteSequence>
<InstallExecuteSequence>
<Custom Action="LaunchApplication" After="InstallFinalize">AUTOLAUNCHAPP AND NOT Installed</Custom>
</InstallExecuteSequence>

View File

@@ -2,21 +2,51 @@
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="INSTALLDIR">
<Component Id="FirezoneClientIpcService" Guid="a8092ff9-30fa-48bb-9b2d-45fe39d16c66">
<File Id="FirezoneClientIpcServiceExe" Name="firezone-client-ipc.exe" Source="../../firezone-client-ipc.exe" KeyPath="yes" />
<ServiceInstall
Id="FirezoneClientServiceInstaller"
Arguments="run"
Type="ownProcess"
Vital="yes"
Name="FirezoneClientIpcService"
DisplayName="Firezone Client IPC"
Start="auto"
Account="LocalSystem"
ErrorControl="normal"
Interactive="no">
</ServiceInstall>
<ServiceControl Id="FirezoneClientServiceControl" Start="install" Stop="both" Remove="both" Name="FirezoneClientIpcService" Wait="yes" />
<Component Id="RemoveOldFirezoneService" Guid="2cfa312f-9a0a-4a3c-bfe8-c7304f69a467">
<ServiceControl Id="RemoveOldFirezoneService" Name="FirezoneClientIpcService"
Stop="both" Remove="both" Wait="yes" />
<ServiceControl Id="RemoveOldFirezoneDebugService" Name="FirezoneClientIpcServiceDebug"
Stop="both" Remove="both" Wait="yes" />
<!--
Hack to make the rename of the service work:
The Windows installer wants to close already running applications and it detects those by checking
whether the file is in use.
If we rename the service file, it the new name will not be in used and therefore, Windows won't
shut it down prior to installation.
It will however try to shut the GUI process down by sending WM_CLOSE. Firezone typically doesn't
have any Windows open and therefore,
WM_CLOSE does nothing and the installer hangs forever trying to close our program.
To fix this, we install the service binary under the old AND new name to force the installer to
shut down the service.
The duplicate file is removed as part of a custom action in `main.wxs`
-->
<File Id="FirezoneClientIpcServiceExe" Name="firezone-client-ipc.exe"
Source="../../firezone-client-tunnel.exe" KeyPath="yes" />
</Component>
<Component Id="FirezoneClientTunnelService" Guid="bd082abf-bd17-4e6f-aed1-4126a8596ecd">
<File Id="FirezoneClientTunnelServiceExe" Name="firezone-client-tunnel.exe"
Source="../../firezone-client-tunnel.exe" KeyPath="yes" />
<ServiceInstall
Id="FirezoneClientTunnelServiceInstaller"
Arguments="run"
Type="ownProcess"
Vital="yes"
Name="FirezoneClientTunnelService"
DisplayName="Firezone Tunnel Service"
Start="auto"
Account="LocalSystem"
ErrorControl="normal"
Interactive="no">
</ServiceInstall>
<ServiceControl Id="FirezoneClientTunnelServiceControl" Start="install" Stop="both"
Remove="both" Name="FirezoneClientTunnelService" Wait="yes" />
</Component>
</DirectoryRef>
</Fragment>

View File

@@ -157,7 +157,7 @@ fn main() -> Result<()> {
assert!(std::env::var(TOKEN_ENV_KEY).is_err());
// TODO: This might have the same issue with fatal errors not getting logged
// as addressed for the IPC service in PR #5216
// as addressed for the Tunnel service in PR #5216
let (layer, _handle) = cli
.log_dir
.as_deref()
@@ -169,7 +169,7 @@ fn main() -> Result<()> {
// in case a previous run of Firezone left DNS control on and messed anything up.
let dns_control_method = cli.dns_control;
let mut dns_controller = DnsController { dns_control_method };
// Deactivate Firezone DNS control in case the system or IPC service crashed
// Deactivate Firezone DNS control in case the system or Tunnel service crashed
// and we need to recover. <https://github.com/firezone/firezone/issues/4899>
dns_controller.deactivate()?;

View File

@@ -1,6 +1,6 @@
//! Implementation of headless Client and IPC service for Windows
//! Implementation of headless Client and Tunnel service for Windows
//!
//! Try not to panic in the IPC service. Windows doesn't consider the
//! Try not to panic in the Tunnel service. Windows doesn't consider the
//! service to be stopped even if its only process ends, for some reason.
//! We must tell Windows explicitly when our service is stopping.

View File

@@ -1,6 +1,6 @@
// Invoke with `cargo run --bin gui-smoke-test`
//
// Starts up the IPC service and GUI app and lets them run for a bit
// Starts up the Tunnel service and GUI app and lets them run for a bit
use anyhow::{Context as _, Result, bail};
use clap::Parser;
@@ -14,7 +14,7 @@ use subprocess::Exec;
const FZ_GROUP: &str = "firezone-client";
const GUI_NAME: &str = "firezone-gui-client";
const IPC_NAME: &str = "firezone-client-ipc";
const TUNNEL_NAME: &str = "firezone-client-tunnel";
#[cfg(target_os = "linux")]
const EXE_EXTENSION: &str = "";
@@ -40,22 +40,22 @@ fn main() -> Result<()> {
dump_syms().context("Failed to run `dump_syms`")?;
// Run normal smoke test
let mut ipc_service = ipc_service_command().arg("run-smoke-test").popen()?;
let mut ipc_service = tunnel_service_command().arg("run-smoke-test").popen()?;
let mut gui = app
.gui_command(&["smoke-test"])? // Disable deep links because they don't work in the headless CI environment
.popen()?;
gui.wait()?.fz_exit_ok().context("GUI process")?;
ipc_service.wait()?.fz_exit_ok().context("IPC service")?;
ipc_service.wait()?.fz_exit_ok().context("Tunnel service")?;
// Force the GUI to crash
let mut ipc_service = ipc_service_command().arg("run-smoke-test").popen()?;
let mut ipc_service = tunnel_service_command().arg("run-smoke-test").popen()?;
let mut gui = app.gui_command(&["--crash"])?.popen()?;
// Ignore exit status here since we asked the GUI to crash on purpose
gui.wait()?;
ipc_service.wait()?.fz_exit_ok().context("IPC service")?;
ipc_service.wait()?.fz_exit_ok().context("Tunnel service")?;
if cli.manual_tests {
manual_tests(&app)?;
@@ -70,12 +70,12 @@ fn manual_tests(app: &App) -> Result<()> {
.popen()?
.wait()?;
let mut ipc_service = ipc_service_command().arg("run-smoke-test").popen()?;
let mut ipc_service = tunnel_service_command().arg("run-smoke-test").popen()?;
let mut gui = app.gui_command(&["--quit-after", "10"])?.popen()?;
// Expect exit codes of 0
gui.wait()?.fz_exit_ok().context("GUI process")?;
ipc_service.wait()?.fz_exit_ok().context("IPC service")?;
ipc_service.wait()?.fz_exit_ok().context("Tunnel service")?;
Ok(())
}
@@ -184,7 +184,7 @@ fn debug_db_path() -> PathBuf {
}
#[cfg(target_os = "linux")]
fn ipc_service_command() -> Exec {
fn tunnel_service_command() -> Exec {
Exec::cmd("sudo").args(&[
"--preserve-env",
"runuser", // The `runuser` looks redundant but CI will complain if we use `sudo` directly, not sure why
@@ -193,15 +193,15 @@ fn ipc_service_command() -> Exec {
"--group",
"firezone-client",
"--whitelist-environment=RUST_LOG",
ipc_path()
tunnel_path()
.to_str()
.expect("IPC binary path should be valid Unicode"),
])
}
#[cfg(target_os = "windows")]
fn ipc_service_command() -> Exec {
Exec::cmd(ipc_path())
fn tunnel_service_command() -> Exec {
Exec::cmd(tunnel_path())
}
// `ExitStatus::exit_ok` is nightly, so we add an equivalent here
@@ -225,10 +225,10 @@ fn gui_path() -> PathBuf {
.with_extension(EXE_EXTENSION)
}
fn ipc_path() -> PathBuf {
fn tunnel_path() -> PathBuf {
Path::new("target")
.join("debug")
.join(IPC_NAME)
.join(TUNNEL_NAME)
.with_extension(EXE_EXTENSION)
}

View File

@@ -4,7 +4,7 @@
set -euox pipefail
SERVICE_NAME=firezone-client-ipc
SERVICE_NAME=firezone-client-tunnel
function debug_exit() {
systemctl status "$SERVICE_NAME"
@@ -21,12 +21,12 @@ dpkg --listfiles firezone-client-gui
dpkg --info "$DEB_PATH"
# Confirm that both binaries and at least one icon were installed
which firezone-client-gui firezone-client-ipc
which firezone-client-gui firezone-client-tunnel
stat /usr/share/icons/hicolor/512x512/apps/firezone-client-gui.png
# Make sure the binary got built, packaged, and installed, and at least
# knows its own name
firezone-client-gui --help | grep "Usage: firezone-client-gui"
# Make sure the IPC service is running
# Make sure the Tunnel service is running
systemctl status "$SERVICE_NAME" || debug_exit

View File

@@ -5,5 +5,5 @@ set -euox pipefail
msiexec //i "$BINARY_DEST_PATH.msi" //log install.log //qn
# For debugging
cat install.log
# Make sure the IPC service is running
sc query FirezoneClientIpcService | grep RUNNING
# Make sure the Tunnel service is running
sc query FirezoneClientTunnelService | grep RUNNING

View File

@@ -5,9 +5,9 @@
source "./scripts/tests/lib.sh"
BINARY_NAME=firezone-client-ipc
BINARY_NAME=firezone-client-tunnel
FZ_GROUP="firezone-client"
SERVICE_NAME=firezone-client-ipc
SERVICE_NAME=firezone-client-tunnel
SOCKET=/run/dev.firezone.client/tunnel.sock
export RUST_LOG=info
@@ -25,7 +25,7 @@ sudo cp "rust/target/debug/$BINARY_NAME" "/usr/bin/$BINARY_NAME"
# Set up the systemd service
sudo cp "rust/gui-client/src-tauri/deb_files/$SERVICE_NAME.service" /usr/lib/systemd/system/
sudo cp "scripts/tests/systemd/env" "/etc/default/firezone-client-ipc"
sudo cp "scripts/tests/systemd/env" "/etc/default/firezone-client-tunnel"
# The firezone group must exist before the daemon starts
sudo groupadd "$FZ_GROUP"

View File

@@ -15,7 +15,7 @@ by advanced users and admins.
| Component | Log directory |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| macOS Client | `~/Library/Group Containers/47R2M6779T.dev.firezone.firezone/Library/Caches/logs` for the GUI process, and `/private/var/root/Library/Group Containers/47R2M6779T.dev.firezone.firezone/Library/Caches/logs` for the tunnel process |
| Windows GUI Client | `%LOCALAPPDATA%\dev.firezone.client\data\logs` for the GUI process, and `%PROGRAMDATA%\dev.firezone.client\data\logs` for the IPC service |
| Windows GUI Client | `%LOCALAPPDATA%\dev.firezone.client\data\logs` for the GUI process, and `%PROGRAMDATA%\dev.firezone.client\data\logs` for the Tunnel service |
| Linux GUI Client | `$HOME/.cache/dev.firezone.client/data/logs/` for the GUI process, and `/var/log/dev.firezone.client/` for the tunnel process |
| Android/ChromeOS Client | `/data/data/dev.firezone.android/caches/logs` |
| iOS Client | N/A |

View File

@@ -65,7 +65,7 @@ already work properly.
from being auto-removed if Firezone is removed)
3. `sudo dnf install ./firezone-client-gui-*.rpm`
4. `sudo usermod -aG firezone-client $USER`
5. `sudo systemctl enable firezone-client-ipc.service` (See
5. `sudo systemctl enable firezone-client-tunnel.service` (See
https://www.freedesktop.org/software/systemd/man/latest/systemd.preset.html,
"It is not recommended to ship preset files within the respective software
packages implementing the units". The Fedora family of distros also seem to
@@ -153,12 +153,12 @@ To export or clear your logs:
1. Click `Diagnostic Logs`.
1. Click `Export Logs` or `Clear Log Directory`.
The IPC service (`firezone-client-ipc.service`) also logs to stdout which is
captured by systemd and sent to journald. To view the logs of the IPC service,
The Tunnel service (`firezone-client-tunnel.service`) also logs to stdout which is
captured by systemd and sent to journald. To view the logs of the Tunnel service,
use:
```bash
journalctl --pager-end --follow --unit firezone-client-ipc.service
journalctl --pager-end --follow --unit firezone-client-tunnel.service
```
The GUI client logs to journald directly as well with the syslog identifier
@@ -242,15 +242,15 @@ If the network interface stays up and DNS does not revert, you can try
restarting the tunnel service. Quit the Firezone GUI, then run:
```bash
sudo systemctl restart firezone-client-ipc
sudo systemctl restart firezone-client-tunnel
```
### Viewing logs
The Firezone Client is split into 2 main processes: An IPC service which runs
The Firezone Client is split into 2 main processes: A Tunnel service which runs
the tunnel, and a GUI which allows the user to control Firezone.
- IPC service logs are stored at `/var/log/dev.firezone.client/`
- Tunnel service logs are stored at `/var/log/dev.firezone.client/`
- GUI logs are stored at `$HOME/.cache/dev.firezone.client/data/logs/`, where
`$HOME` is, e.g. `/home/username/`

View File

@@ -127,13 +127,13 @@ To fix, perform these steps:
`Settings -> Network and Internet -> Additional network settings -> Network Reset -> Reset now`.
3. Reinstall Firezone and any other software you previously uninstalled.
### Check if Firezone Client IPC service is running
### Check if Firezone Client Tunnel service is running
In the Start Menu, search for "Windows Powershell". Open it and run this
command:
```pwsh
Get-Service -Name FirezoneClientIpcService
Get-Service -Name FirezoneClientTunnelService
```
Good output
@@ -202,10 +202,10 @@ Get-DnsClientNrptRule | where Comment -eq firezone-fd0020211111 | foreach { Remo
### Viewing logs
The Firezone Client is split into 2 main processes: An IPC service which runs
The Firezone Client is split into 2 main processes: A Tunnel service which runs
the tunnel, and a GUI which allows the user to control Firezone.
- IPC service logs are stored at `%PROGRAMDATA%\dev.firezone.client\data\logs\`,
- Tunnel service logs are stored at `%PROGRAMDATA%\dev.firezone.client\data\logs\`,
where `%PROGRAMDATA%` is almost always `C:\ProgramData`
- GUI logs are stored at `%LOCALAPPDATA%\dev.firezone.client\data\logs`, where
`%LOCALAPPDATA%` is, e.g. `C:\Users\username\AppData\Local`

View File

@@ -17,6 +17,18 @@ export default function GUI({ os }: { os: OS }) {
Launching Firezone while it is already running while now re-activate
the "Welcome" screen, allowing the user to sign in and out.
</ChangeItem>
{os === OS.Windows && (
<ChangeItem pull="9154">
Renames the background service from `FirezoneClientIpcService`
to `FirezoneClientTunnelService`.
</ChangeItem>
)}
{os === OS.Linux && (
<ChangeItem pull="9154">
Renames the systemd service from `firezone-client-ipc.service`
to `firezone-client-tunnel.service`.
</ChangeItem>
)}
</Unreleased>
<Entry version="1.4.13" date={new Date("2025-05-14")}>
<ChangeItem pull="9014">