diff --git a/examples/systemd/vnet/README.md b/examples/systemd/vnet/README.md
new file mode 100644
index 0000000000000..6deb8c83b7f37
--- /dev/null
+++ b/examples/systemd/vnet/README.md
@@ -0,0 +1,33 @@
+# Teleport VNet Linux Files
+
+This directory contains files needed for VNet to work on Linux.
+Teleport Connect ships these files in its package.
+
+## Files
+
+- `teleport-vnet.service`: systemd unit for the privileged VNet daemon.
+- `dbus/org.teleport.vnet1.conf`: D-Bus system bus policy for `org.teleport.vnet1`.
+- `dbus/org.teleport.vnet1.service`: D-Bus service activation entry for `org.teleport.vnet1`.
+- `polkit/org.teleport.vnet1.policy`: polkit policy used to authorize starting and stopping the privileged VNet daemon.
+
+## Install locations (package defaults)
+
+- `teleport-vnet.service` -> `/usr/lib/systemd/system/teleport-vnet.service`
+- `dbus/org.teleport.vnet1.conf` -> `/usr/share/dbus-1/system.d/org.teleport.vnet1.conf`
+- `dbus/org.teleport.vnet1.service` -> `/usr/share/dbus-1/system-services/org.teleport.vnet1.service`
+- `polkit/org.teleport.vnet1.policy` -> `/usr/share/polkit-1/actions/org.teleport.vnet1.policy`
+
+Notes:
+- For packaged vendor files, `/usr/share/...` is the standard location.
+- `/etc/dbus-1/system.d/` is typically for local admin overrides, not vendor package files.
+
+## Manual install example
+
+```bash
+sudo cp teleport-vnet.service /usr/lib/systemd/system/teleport-vnet.service
+sudo cp dbus/org.teleport.vnet1.conf /usr/share/dbus-1/system.d/org.teleport.vnet1.conf
+sudo cp dbus/org.teleport.vnet1.service /usr/share/dbus-1/system-services/org.teleport.vnet1.service
+sudo cp polkit/org.teleport.vnet1.policy /usr/share/polkit-1/actions/org.teleport.vnet1.policy
+sudo systemctl daemon-reload
+sudo dbus-send --print-reply --system --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ReloadConfig
+```
diff --git a/examples/systemd/vnet/dbus/org.teleport.vnet1.conf b/examples/systemd/vnet/dbus/org.teleport.vnet1.conf
new file mode 100644
index 0000000000000..c6da201ab42b4
--- /dev/null
+++ b/examples/systemd/vnet/dbus/org.teleport.vnet1.conf
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/systemd/vnet/dbus/org.teleport.vnet1.service b/examples/systemd/vnet/dbus/org.teleport.vnet1.service
new file mode 100644
index 0000000000000..ff67eb869c4bd
--- /dev/null
+++ b/examples/systemd/vnet/dbus/org.teleport.vnet1.service
@@ -0,0 +1,5 @@
+[D-BUS Service]
+Name=org.teleport.vnet1
+SystemdService=teleport-vnet.service
+User=root
+Exec=/bin/false
diff --git a/examples/systemd/vnet/polkit/org.teleport.vnet1.policy b/examples/systemd/vnet/polkit/org.teleport.vnet1.policy
new file mode 100644
index 0000000000000..49f6a1f2d4777
--- /dev/null
+++ b/examples/systemd/vnet/polkit/org.teleport.vnet1.policy
@@ -0,0 +1,18 @@
+
+
+
+
+
+ Start Teleport VNet
+ Authentication is required to start Teleport VNet
+
+ no
+ no
+
+ yes
+
+
+
+
diff --git a/examples/systemd/vnet/teleport-vnet.service b/examples/systemd/vnet/teleport-vnet.service
new file mode 100644
index 0000000000000..bd1db2e775343
--- /dev/null
+++ b/examples/systemd/vnet/teleport-vnet.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Teleport VNet D-Bus service
+After=dbus.service
+Requires=dbus.service
+
+[Service]
+Type=dbus
+BusName=org.teleport.vnet1
+ExecStart=/usr/local/bin/tsh vnet-daemon
+User=root
+Group=root
diff --git a/web/packages/teleterm/build_resources/linux/after-install.sh.tmpl b/web/packages/teleterm/build_resources/linux/after-install.sh.tmpl
index fb2385535ac0d..aa2e66e489155 100644
--- a/web/packages/teleterm/build_resources/linux/after-install.sh.tmpl
+++ b/web/packages/teleterm/build_resources/linux/after-install.sh.tmpl
@@ -105,4 +105,44 @@ else
fi
fi
+has_systemd() {
+ [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1
+}
+
+install_vnet_file() {
+ src="$1"
+ dst="$2"
+ mode="$3"
+
+ [ -f "$src" ] || return 0
+ mkdir -p "$(dirname "$dst")"
+ install -m "$mode" "$src" "$dst"
+}
+
+reload_dbus_config() {
+ # Normally packages that install files into
+ # D-Bus configuration directories (for example):
+ # /usr/share/dbus-1/system.d/
+ # /usr/share/dbus-1/system-services/
+ #
+ # rely on package manager to reload the system bus configuration after installation.
+ #
+ # In our case, these files are copied into place by post-install script, so we reload
+ # dbus config manually.
+ dbus-send --print-reply --system \
+ --dest=org.freedesktop.DBus \
+ /org/freedesktop/DBus \
+ org.freedesktop.DBus.ReloadConfig >/dev/null 2>&1 || true
+}
+
+if has_systemd; then
+ VNET_SOURCE_DIR=$APP/resources/vnet
+ install_vnet_file "$VNET_SOURCE_DIR/polkit/org.teleport.vnet1.policy" /usr/share/polkit-1/actions/org.teleport.vnet1.policy 0644
+ install_vnet_file "$VNET_SOURCE_DIR/dbus/org.teleport.vnet1.conf" /usr/share/dbus-1/system.d/org.teleport.vnet1.conf 0644
+ install_vnet_file "$VNET_SOURCE_DIR/dbus/org.teleport.vnet1.service" /usr/share/dbus-1/system-services/org.teleport.vnet1.service 0644
+ install_vnet_file "$VNET_SOURCE_DIR/teleport-vnet.service" /usr/lib/systemd/system/teleport-vnet.service 0644
+ systemctl daemon-reload || true
+ reload_dbus_config
+fi
+
# vim: syntax=sh
diff --git a/web/packages/teleterm/build_resources/linux/after-remove.sh.tmpl b/web/packages/teleterm/build_resources/linux/after-remove.sh.tmpl
index 11b4d0b1d88de..6f3f650b41192 100644
--- a/web/packages/teleterm/build_resources/linux/after-remove.sh.tmpl
+++ b/web/packages/teleterm/build_resources/linux/after-remove.sh.tmpl
@@ -54,6 +54,40 @@ if [ -L "$TSH_SYMLINK_TARGET" ] && [ ! -e "$TSH_SYMLINK_TARGET" ]; then
rm -f "$TSH_SYMLINK_TARGET"
fi
+has_systemd() {
+ [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1
+}
+
+remove_vnet_file() {
+ dst="$1"
+ rm -f "$dst"
+}
+
+reload_dbus_config() {
+ # Normally packages that install files into
+ # D-Bus configuration directories (for example):
+ # /usr/share/dbus-1/system.d/
+ # /usr/share/dbus-1/system-services/
+ #
+ # rely on package manager to reload the system bus configuration after installation.
+ #
+ # In our case, these files are copied into place by post-install script, so we reload
+ # dbus config manually.
+ dbus-send --print-reply --system \
+ --dest=org.freedesktop.DBus \
+ /org/freedesktop/DBus \
+ org.freedesktop.DBus.ReloadConfig >/dev/null 2>&1 || true
+}
+
+if has_systemd; then
+ remove_vnet_file /usr/share/polkit-1/actions/org.teleport.vnet1.policy
+ remove_vnet_file /usr/share/dbus-1/system.d/org.teleport.vnet1.conf
+ remove_vnet_file /usr/share/dbus-1/system-services/org.teleport.vnet1.service
+ remove_vnet_file /usr/lib/systemd/system/teleport-vnet.service
+ systemctl daemon-reload || true
+ reload_dbus_config
+fi
+
# shellcheck disable=SC2016 # This is custom electron-builder macro expansion, not Bash templating.
APPARMOR_PROFILE_DEST='/etc/apparmor.d/${executable}'
diff --git a/web/packages/teleterm/electron-builder-config.js b/web/packages/teleterm/electron-builder-config.js
index ade99097601a9..5d649bac844ab 100644
--- a/web/packages/teleterm/electron-builder-config.js
+++ b/web/packages/teleterm/electron-builder-config.js
@@ -1,5 +1,6 @@
const { env, platform } = require('process');
const fs = require('fs');
+const path = require('path');
const { spawn } = require('child_process');
const isMac = platform === 'darwin';
const isWindows = platform === 'win32';
@@ -86,9 +87,9 @@ module.exports = {
return;
}
- const path = `${packed.appOutDir}/Teleport Connect.app/Contents/MacOS/tsh.app/Contents/Info.plist`;
+ const plistPath = `${packed.appOutDir}/Teleport Connect.app/Contents/MacOS/tsh.app/Contents/Info.plist`;
if (packed.appOutDir.endsWith('mac-universal-x64-temp')) {
- tshAppPlist = fs.readFileSync(path);
+ tshAppPlist = fs.readFileSync(plistPath);
}
if (packed.appOutDir.endsWith('mac-universal')) {
if (!tshAppPlist) {
@@ -96,7 +97,7 @@ module.exports = {
'Failed to copy tsh.app Info.plist file from the x64 build. Check if the path "mac-universal-x64-temp" was not changed by electron-builder.'
);
}
- fs.writeFileSync(path, tshAppPlist);
+ fs.writeFileSync(plistPath, tshAppPlist);
}
},
files: ['build/app'],
@@ -259,6 +260,34 @@ module.exports = {
from: env.CONNECT_TSH_BIN_PATH,
to: './bin/tsh',
},
+ {
+ from: path.resolve(
+ __dirname,
+ '../../../examples/systemd/vnet/polkit/org.teleport.vnet1.policy'
+ ),
+ to: './vnet/polkit/org.teleport.vnet1.policy',
+ },
+ {
+ from: path.resolve(
+ __dirname,
+ '../../../examples/systemd/vnet/dbus/org.teleport.vnet1.conf'
+ ),
+ to: './vnet/dbus/org.teleport.vnet1.conf',
+ },
+ {
+ from: path.resolve(
+ __dirname,
+ '../../../examples/systemd/vnet/dbus/org.teleport.vnet1.service'
+ ),
+ to: './vnet/dbus/org.teleport.vnet1.service',
+ },
+ {
+ from: path.resolve(
+ __dirname,
+ '../../../examples/systemd/vnet/teleport-vnet.service'
+ ),
+ to: './vnet/teleport-vnet.service',
+ },
{
from: 'build_resources/linux/apparmor-profile',
to: './apparmor-profile',