Skip to content

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.

apt-get --yes install kde-plasma-desktop xrdp ssl-cert

Add the user xrdp to the group ssl-cert.

usermod -aG ssl-cert xrdp

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

systemctl status accounts-daemon

Create the directory

mkdir --parents /usr/share/accountsservice/interfaces

Start the service

systemctl start accounts-daemon

Add pam_wallet5 cinfiguration module to file /etc/pam.d/xrdp-sesman just before the line with @include common-auth.

auth optional pam_kwallet5.so
session optional pam_kwallet5.so auto_start

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.

nmcli con add type dummy con-name fake ifname fake0 ip4 1.2.3.4/24 gw4 1.2.3.1

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:

systemctl restart polkit

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.

fc-cache -f -v

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.

Package: snapd
Pin: release a=*
Pin-Priority: -10

Command Line

Set persistent permissions on a directory

Set a directory to be readable by the group mygroup.

setfacl --default --modify=g:mygroup:rX my/directory
setfacl -d -m g:mygroup:rX my/directory

Set a directory owner and let the content read- and writeable by the group mygroup.

chown admin:admin my/directory
setfacl -m g:mygroup:rwX,d:g:mygroup:rwX --default my/directory

Set a directory to be readwrite by the group mygroup and any new files and subdirectories created in that directory.

setfacl -Rm g:mygroup:rwX,d:g:mygroup:rwX my/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