LXC with KDE Plasma and Remote Desktop
Host Configuration
Besides the regular LXC container configuration described here Some additional steps have to be taken.
File: /var/lib/lxc/kde/config
The full configuration should look like this.
# Template used to create this container: /usr/share/lxc/templates/lxc-download
# Parameters passed to the template:
# For additional config options, please look at lxc.container.conf(5)
# Uncomment the following line to support nesting containers:
#lxc.include = /usr/share/lxc/config/nesting.conf
# (Be aware this has security implications)
# Distribution configuration
lxc.include = /usr/share/lxc/config/common.conf
lxc.arch = linux64
# Container specific configuration
#lxc.apparmor.profile = generated
lxc.apparmor.allow_nesting = 1
lxc.rootfs.path = dir:/var/lib/lxc/kde/rootfs
lxc.uts.name = kde
# Network configuration
lxc.net.0.type = veth
lxc.net.0.link = lxcbr0
lxc.net.0.hwaddr = 00:16:3e:ab:b6:e6
lxc.net.0.flags = up
# Enable auto start of this container.
lxc.start.auto = 1
# Delay start this container 45 seconds from when the host starts.
lxc.start.delay = 15
# Add iptables rule when starting and stopping.
lxc.hook.pre-start = /var/lib/lxc/kde/port-nat-hook.sh delay pre
lxc.hook.stop = /var/lib/lxc/kde/port-nat-hook.sh post
# Changes to allow access to eth0 and allow PackageKit to update packages.
lxc.apparmor.profile = unconfined
lxc.cgroup.devices.allow = c 10:200 rwm
# Allow fuse without errors/warnings.
lxc.autodev = 1
lxc.hook.autodev = sh -c "mknod -m 0666 ${LXC_ROOTFS_MOUNT}/dev/fuse c 10 229"
The hook script will make the port on the host be translated to the KDE LXC container where options delay delays
the pre action so the lxc-info command does not interlock with the lxc-start command.
#!/bin/bash
# Sanity check on arguments.
if [[ -z "${1}" ]]; then
echo "Argument missing.
Usage in LXC container 'config' file:
lxc.hook.pre-start = /var/lib/lxc/<container-name>/port-nat-hook.sh delay pre
lxc.hook.stop = /var/lib/lxc/<container-name>/port-nat-hook.sh post
"
exit 1
fi
# When delayed execution run it in the background.
if [[ "$1" == "delay" ]]; then
# Remove the 'delay' argument.
shift 1
# Run this script in the background and detached from the parent.
"$0" "$@" &> /dev/null & disown %-
# Exit this script normally.
exit 0
fi
# Port to nat from host interface to LXC container ip.
from_port=3389
to_port=3389
# Form the rule as an array of options.
rule=(PREROUTING --table nat --protocol tcp --dport "${from_port}" --jump DNAT --to-destination)
# Form the filename for this container.
ipv4_file="/tmp/lxc-$LXC_NAME-ipv4-addres.txt"
# Create a rule.
if [[ "$1" == "pre" ]]; then
# Allow container to start and receive an ip.
sleep 3
# Get the ipv4 info from an other wise locking up command 'lxc-info' when in the foreground.
ipv4="$(lxc-info -in "${LXC_NAME}" | grep -o '[^ ]*$')"
# Create a signature file containing the containers ip-address.
echo -e "${ipv4}" > "${ipv4_file}"
# Finish the rule with the destination ip.
rule+=("${ipv4}:${to_port}")
# When the rule does not exist create it.
if ! iptables --check "${rule[@]}"; then
iptables --append "${rule[@]}"
fi
# Remove rule when the ipv4 file exists.
elif [[ "$1" == "post" && -f "${ipv4_file}" ]]; then
# Read the ip from the file.
IFS= read -r ipv4 < "${ipv4_file}"
# Finish the rule with the destination ip.
rule+=("${ipv4}:${to_port}")
# When the rule exists delete it.
if iptables --check "${rule[@]}"; then
iptables --delete "${rule[@]}"
fi
# Remove the file ipv4 just in case?
"${ipv4_file}"
fi
Guest OS Configuration
General
Install the package kde-desktop and xrdp to allow login in using the Remote Desktop protocol.
Add the user xrdp to the group ssl-cert.
Improve performance of the xrdp daeomon by adding or changing the following lines
in file /etc/xrdp/xrdp.ini.
# 4MB buffer size for xrdp.
tcp_send_buffer_bytes=4194304
tcp_recv_buffer_bytes=4194304
# Minimum security level allowed for client.
crypt_level=low
Due to missing directory /usr/share/accountsservice/interfaces the service accounts-daemon.service
is not running in the setup using LXC.
Check if the service is running
Create the directory
Start the service
Add pam_wallet5 cinfiguration module to file /etc/pam.d/xrdp-sesman just before
the line with @include common-auth.
After this the wallet is open at login, but the wallet needs to have the same password as the user login.
Restart the xrdp service or reboot the container to make it have an effect.
Cron @reboot Workaround
The cron entry @reboot is not working in the container Guest OS. To work around this issue, a systemd service is added.
Create the oneshot typed service file /etc/systemd/system/lxc-boot-script.service using the folowing content.
`[Unit]
Description=LXC Boot Script Execution
After=network.target
[Service]
Type=oneshot
ExecStart=/root/bin/boot-script.sh
RemainAfterExit=yes
[User]
User=root
[Install]
WantedBy=multi-user.target`
The script /root/bin/boot-script.sh is now executed when the container Guest OS starts.
Fake Network Connection
For some reason to have the eth0 show as a Wired Connection 1 on the desktop in a xrdp-session a fake connection
is needed.
Without it the PackageKit which is used by the 'Plasma Discover' application is not able to update packages.
Add PolicyKit rules
Since Ubuntu 24.04 LTS the PolicyKit uses javascript files for configuring rules.
After adding or changing a rule, restart the service using:
Polkit uses a lightweight JavaScript interpreter (based on Mozilla's SpiderMonkey engine) to evaluate the rules.
PackageKit: Refresh
To allow a user to update/refresh package information without logging in create a
file /etc/polkit-1/rules.d/50-packagekit-refresh.rules and enter the following.
polkit.addRule(function (action, subject) {
if (([
"org.freedesktop.packagekit.system-sources-refresh",
"org.freedesktop.packagekit.package-install",
"org.freedesktop.packagekit.upgrade-system",
"org.freedesktop.packagekit.trigger-offline-update"
].indexOf(action.id) !== -1) &&
subject.isInGroup("sudo")) {
return polkit.Result.YES;
}
});
The part subject.isInGroup("sudo") checks if the user is in the group of sudoers.
Network: Workaround for PackageKit
In the container Guest OS create a file /etc/polkit-1/rules.d/50-xrdp-network.rules.
This prevents the user from modifying the network configuration or disconnecting network connections
just needed for the proper working of the PackageKit software.
polkit.addRule(function (action, subject) {
if (([
"org.freedesktop.NetworkManager.settings.modify.system",
"org.freedesktop.NetworkManager.network-control",
"org.freedesktop.NetworkManager.enable-disable-network",
"org.freedesktop.NetworkManager.enable-disable-wifi",
"org.freedesktop.NetworkManager.sleep-wake"
].indexOf(action.id) !== -1) && subject.local == false && subject.active == true) {
return polkit.Result.NO;
}
});
Where:
subject.local == false: Checks if the session is remote.subject.active == true: Ensures the rule only applies to active sessions.
Power: Disable Power-Off & Reboot Buttons
In the container Guest OS create a file /etc/polkit-1/rules.d/50-xrdp-shutdown-reboot.rules to
create a Polkit rule to restrict Reboot and Power-Off options from the menu.
polkit.addRule(function (action, subject) {
if (([
"org.freedesktop.login1.reboot",
"org.freedesktop.login1.reboot-multiple-sessions",
"org.freedesktop.login1.power-off",
"org.freedesktop.login1.power-off-multiple-sessions",
"org.freedesktop.login1.suspend",
"org.freedesktop.login1.hibernate"
].indexOf(action.id) !== -1) && subject.local == false && subject.active == true) {
return polkit.Result.NO;
}
});
Allow Changing User Data & Password
In the container Guest OS create a file `/etc/polkit-1/rules.d/50-change-user-data.rules`.
polkit.addRule(function (action, subject) {
if (([
"org.freedesktop.accounts.change-own-user-data",
"org.freedesktop.accounts.change-own-password",
"org.freedesktop.timedate1.set-timezone"
].indexOf(action.id) !== -1) && subject.local == false && subject.active == true) {
return polkit.Result.YES;
}
});
polkit.addRule(function (action, subject) {
if (([
"org.freedesktop.accounts.user-administration",
"org.freedesktop.accounts.set-login-option",
"org.freedesktop.timedate1.set-ntp",
"org.freedesktop.timedate1.set-time"
].indexOf(action.id) !== -1) && subject.isInGroup("sudo")) {
return polkit.Result.YES;
}
});
Additional Packages
Some additional packages are needed for some applications and KDE plugins.
apt install wget joe mc git bash-completion sshfs xterm fuse sshfs cups printer-settings cups-pdf ark \
language-selector-common plasma-workspace-data rdiff-backup telnet cifs-utils printer-manager hplip \
nmap snmp kdeconnect install ttf-mscorefonts-installer fonts-noto-color-emoji xdg-desktop-portal-kde
Run the next command to make the fonts available.
Prevent Snap Packages
To prevent snap packages from being installed on the guest OS, create the file
/etc/apt/preferences.d/no-snap with the following content.
Command Line
Set persistent permissions on a directory
Set a directory to be readable by the group mygroup.
Set a directory owner and let the content read- and writeable by the group mygroup.
Set a directory to be readwrite by the group mygroup and any new files and subdirectories created in that directory.
Workaround: Keyboard Layout Activation
After reconnecting to the xrdp session is not activated until a system setting is changed and applied.
To trigger the same response the initially empty /etc/xrdp/reconnectwm.sh is used.
#!/bin/bash
# Fix for reconnection keyboard layout using xrdp.
# File: /etc/xrdp/reconnectwm.sh
# XRDP natively populates $DISPLAY and $USER variables for the executing session.
# If $USER is missing for any reason, find the exact user owning the current X server display port.
if [ -z "$USER" ]; then
USER=$(stat -c '%U' /tmp/.X11-unix/X${DISPLAY#:*})
fi
# Extract the unique Numeric User ID (UID) for this specific user.
USER_ID=$(id -u "$USER")
# Define the exact path to this user's systemd user-space D-Bus broker socket
USER_DBUS="unix:path=/run/user/$USER_ID/bus"
# Execute the refresh cleanly without hardcoded delays or global environment bleeding.
if [ -n "$USER" ] && [ -S "/run/user/$USER_ID/bus" ]; then
(
# Delay briefly to let xorgxrdp complete the client-side rendering handshake.
sleep 2
# Inject the command straight into the target user's private DBus architecture.
env DISPLAY="$DISPLAY" DBUS_SESSION_BUS_ADDRESS="$USER_DBUS" \
dbus-send --session --type=signal --reply-timeout=100 --dest=org.kde.keyboard /Layouts org.kde.keyboard.reloadConfig
# Notify the user.
env DISPLAY="$DISPLAY" \
kdialog --title "Keyboard Layout" --passivepopup "Keyboard layout reload triggered..." 3
) &
fi
Connect using FreeRDP
Install the package freerdp or freerdp3 on Linux.
FreeRDP is an opensource and free remote desktop client application.
Options between the versions are not all compatible, so below are two sets of options to make it work optimally.
Compared to the Windows Remote Desktop Client the option /dynamic-resolution allows resizing the
desktop on command even when connecting to Windows guest OSes.
Application xfreerdp Version 2
Recommended options for application version 2:
xfreerdp \
-bpp:32 \
/dynamic-resolution \
/cert-ignore \
+offscreen-cache \
/gfx:RFX \
+gfx-progressive \
+glyph-cache \
+window-drag \
+video \
/sound:sys:pulse \
/microphone:sys:pulse \
+home-drive \
/gdi:hw \
/t:"FreeRDP v2 (ubuntu@10.0.3.210)" \
/size:1910x1460 \
/v:10.0.3.210:3389 \
/u:ubuntu \
+fonts \
+aero \
+clipboard \
/compression-level:2 \
/log-level:OFF \
/from-stdin:force
Application xfreerdp3 Version 3
Recommended options for application version 3:
xfreerdp3 \
-bpp:32 \
/dynamic-resolution \
/cert:ignore \
/cache:bitmap:on,offscreen:on,progressive:on,glyph:on,RFX:on,thin-client:on \
/d: +window-drag \
+video \
/sound:sys:pulse \
/microphone:sys:pulse \
+home-drive \
/gdi:hw \
/t:"FreeRDP v3 (ubuntu@10.0.3.210)" \
/size:1900x800 \
/v:10.0.3.210:3389 \
/u:ubuntu \
+fonts \
+aero \
+clipboard \
/compression-level:2 \
/log-level:OFF \
/from-stdin:force
Installing Samba (server)
samba-common-bin samba-common samba-common-libs samba-server samba samba-client