Hi everyone,
I’m new working with NixOS , so please be kind to newbies… ![]()
We are currently working on a scientific test setup using and
connecting some llm and AI services. To ensure a declarative
approach, we are going to implement a declarative approach based on
NixOS 25.11 guests running on a Windows Server 2019 HyperV host
in order to establish a small mesh of internal VMs behind an edge-VM
to the internet and leave the maintenance to admins/users used to
Microsoft infrastructure. To ease setups and operations, we have
used a NixOS hybrid configuration so that the base configuration is
defined and we use flake.nix on each of the VMs to customize services
and modules.
In order to ensure reliabity and security on the host and the guests,
we’ve tried to change the UI of the Guest-VMs according to a role scheme
- red → infrastructure,
- yellow → services,
- green → static machines (mainly data)
Some (standard) VMs for users and user agents should stay with the
Breeze scheme of sddm standard/KDE without changes.
Thus we have tried to implement ‘UI-branding’ by a small ‘screenlayout.nix’
which is loaded by flake.nix, having the hope that within the HyperV
console we can use color codes and in addition hostname + ipv4-address
in order to ease life of the admins. The same should work with
wallpapers within RDP session (the users are used to Terminalservices
on their MS machines…), i.e. we want to realize two aspects:
- Hyper‑V console on the server (the built‑in VM console) with a
colorized UI/theme and additional info (hostname + ipv4) on the
logon screen - RDP sessions (Plasma/X11) wallpapers + hostname + ipv4 in the top
‘row’ of the window
Below is a summary of what we have achieved so far, what does not work,
and where we are stuck. We would greatly appreciate any technical insights
from people who know the internals of SDDM, Plasma, Hyper‑V, or
NixOS boot/session ordering better than we do.
============================================================
GOAL 1 – Hyper‑V console branding
Intended:
- Show a role‑specific theme (with colorized logon screen, hostname,
ipv4 and after being logged in the wllpaper/info like in RDP) - Display hostname + IPv4 address directly in the console
Reality:
- whatever we did we ended up with MAUI fallback in the console window
- It does not display SDDM, X11, Plasma, QML, images, or panels
- It cannot show colored backgrounds (or themes?)
- It cannot show IP address early in boot (network not up yet)
- It currently also does NOT respect
console.keyMap = "de";(still US layout)
Status:
- Hyper‑V console branding appears technically impossible with the
mechanisms we’ve tried so far - Keyboard layout in Hyper‑V console remains US (open issue)
============================================================
GOAL 2 – RDP session branding (Plasma/X11)
Intended:
- Role‑specific wallpaper (muted color palette)
- Hostname + IPv4 displayed at the top of the wallpaper
- Consistent behavior in every RDP session dependedn on
the role pattern
What we implemented:
- A Nix derivation that generates a wallpaper per role
- A render script that overlays host information from
/run/vm-hostinfo - An autostart script that applies the wallpaper via
plasma-apply-wallpaperimage
Status:
- Works reliably in local Plasma sessions
- Works inconsistently in RDP sessions because:
- RDP starts its own Plasma session
- Autostart runs before
plasmashellis fully ready /run/vm-hostinfomay not exist yet- Timing is fragile
============================================================
SDDM THEMING (for completeness)
- For non‑legacy roles we generate a custom SDDM theme
(color per role, hostinfo text) - For the “legacy” role we intentionally fall back to
the built‑in Breeze theme (i.e. we do not want to change
standards) - On NixOS 25.11, Breeze is automatically available when
Plasma6 + SDDM are enabled - SDDM theming works as expected
============================================================
OPEN QUESTIONS
- Is there any supported way to influence the Hyper‑V console
appearance beyond plain TTY text? - Is there a reliable way to ensure
/run/vm-hostinfoexists
before SDDM and before RDP sessions start? - Is there a robust pattern for Plasma autostart that guarantees
plasmashellis ready (especially in RDP sessions)? - Is there a recommended NixOS idiom for per‑session overlays
(hostname/IP) that works across local + RDP Plasma sessions?
We need some expert advice to simplify the NixOS UI branding and
have some insight into the competing mechanisms…!
Thankx in advance
Rolf
============================================================
As with respect to the UI branding, we have loaded the
following screenlayout.nix:
{ config, pkgs, lib, … }:
let
-----------------------------------------
VM roles
-----------------------------------------
role = “gateway”; # gatewayVM / serviceVM / databaseVM / legacy
-----------------------------------------
color code per role
-----------------------------------------
roleColors = {
gateway = { sddm = “#CC0000”; wall = “#5A0000”; };
service = { sddm = “#F57C00”; wall = “#7A3F00”; };
datab = { sddm = “#107C41”; wall = “#08331F”; };
legacy = null;
};
colors = roleColors.${role};
-----------------------------------------
SDDM-Theme as Nix-derivation
-----------------------------------------
mkSddmTheme = { name, bgColor }:
pkgs.stdenv.mkDerivation {
name = “sddm-theme-${name}”;
src = null;
dontUnpack = true;
installPhase = ''
mkdir -p $out/${name}
# mandatory: metadata.desktop
cat > $out/${name}/metadata.desktop <<EOF
[Desktop Entry]
Name=${name}
Comment=Mesh Theme (${name})
Type=Theme
Version=1.0
MainScript=Main.qml
ConfigFile=theme.conf
EOF
# mandatory: theme.conf
cat > $out/${name}/theme.conf <<EOF
[General]
type=sddm-theme
name=${name}
version=1.0
backgroundColor=${bgColor}
EOF
# main-QML
cat > $out/${name}/Main.qml <<EOF
import QtQuick 2.15
import QtQuick.Controls 2.15
import SddmComponents 2.0
import Qt.labs.qmlmodels 1.0 # file access
Rectangle {
width: Screen.width
height: Screen.height
color: “${bgColor}”
Column {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 40
spacing: 10
Text {
id: hostinfo
color: "white"
font.pointSize: 28
Component.onCompleted: {
var f = File.open("/run/vm-hostinfo", File.ReadOnly);
if (f) {
var content = f.readAll();
f.close();
var lines = content.split("\\n");
var disp = "DISPLAY unavailable";
var ip = "IP unavailable";
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith("DISPLAY="))
disp = lines[i].substring(8);
if (lines[i].startsWith("IPV4="))
ip = lines[i].substring(5);
}
text = disp + " (" + ip + ")";
} else {
text = "Hostinfo unavailable";
}
}
}
}
Column {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: 20
UserList { width: 300 }
PasswordField {
id: password
width: 300
placeholderText: "Password"
onAccepted: sddm.login(userList.currentUser, password.text)
}
Button {
text: "Login"
width: 300
onClicked: sddm.login(userList.currentUser, password.text)
}
}
}
EOF
‘’;
};
-----------------------------------------
dynamic wallpaper → plasma (hostname + ipv4)
-----------------------------------------
mkWallpaper = { name, baseColor }:
pkgs.stdenv.mkDerivation {
name = “wallpaper-${name}”;
buildInputs = [ pkgs.imagemagick ];
src = null;
dontUnpack = true;
installPhase = ''
mkdir -p $out
# generate base picture
magick -size 3840x2160 xc:'${baseColor}' \
\( -size 3840x2160 xc: +noise Random \) \
-compose overlay -define compose:args=7 -composite \
$out/base.png
# render-script -> dynamic data
cat > $out/render.sh <<EOF
#!/bin/sh
. /run/vm-hostinfo
magick $out/base.png
-gravity north
-pointsize 64 -fill white
-annotate +0+80 “$DISPLAY ($IPV4)”
$out/wallpaper.png
EOF
chmod +x $out/render.sh
‘’;
};
sddmTheme =
if role == “legacy” then null
else mkSddmTheme { name = “role-${role}”; bgColor = colors.sddm; };
wallpaper =
if role == “legacy” then null
else mkWallpaper { name = “role-${role}”; baseColor = colors.wall; };
in
{
-----------------------------------------
activate X11
-----------------------------------------
services.xserver.enable = true;
keymap
services.xserver.xkb = {
layout = “de”;
variant = “”;
};
plasma6 desktop
services.desktopManager.plasma6.enable = true;
force SDDM via old X11-module
services.xserver.displayManager.sddm = {
enable = true;
wayland.enable = false;
};
set SDDM-theme
services.displayManager.sddm.theme = “role-${role}”;
services.displayManager.sddm.settings = {
General = {
InputMethod = “”;
EnableVirtualKeyboard = false;
};
X11 = {
Layout = “de”;
Variant = “”;
};
};
-----------------------------------------
install theme to SDDM-path
-----------------------------------------
environment.systemPackages = [
(pkgs.runCommand “install-sddm-theme” {} ‘’
mkdir -p $out/share/sddm/themes/role-${role}
cp -r ${sddmTheme}/role-${role}/* $out/share/sddm/themes/role-${role}/
‘’)
];
-----------------------------------------
dynamic wallpaper → plasma
-----------------------------------------
system.activationScripts.wallpaper = ‘’
mkdir -p /home/vmadmin/.config/autostart
mkdir -p /home/vmadmin/.local/bin
cat > /home/vmadmin/.local/bin/set-wallpaper.sh <<EOF
#!/bin/sh
${wallpaper}/render.sh
plasma-apply-wallpaperimage ${wallpaper}/wallpaper.png
EOF
chmod +x /home/vmadmin/.local/bin/set-wallpaper.sh
chown vmadmin:users /home/vmadmin/.local/bin/set-wallpaper.sh
cat > /home/vmadmin/.config/autostart/set-wallpaper.desktop <<EOF
[Desktop Entry]
Type=Application
Exec=/home/vmadmin/.local/bin/set-wallpaper.sh
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name=Set Dynamic Wallpaper
EOF
chown vmadmin:users /home/vmadmin/.config/autostart/set-wallpaper.desktop
‘’;
-----------------------------------------
overlay-panel → hostinfo
-----------------------------------------
system.activationScripts.overlayPanel = ‘’
mkdir -p /home/vmadmin/.local/share/plasma/layout-templates
mkdir -p /home/vmadmin/.local/bin
mkdir -p /home/vmadmin/.config/autostart
cat > /home/vmadmin/.local/bin/create-overlay-panel.sh <<EOF
#!/bin/bash
---------------------------------------------------------
1. wait for plasma (max. 10 Sekunden)
---------------------------------------------------------
for i in {1..20}; do
if qdbus org.kde.plasmashell /PlasmaShell >/dev/null 2>&1; then
break
fi
sleep 0.5
done
quit if Plasma doesn’t appear
if ! qdbus org.kde.plasmashell /PlasmaShell >/dev/null 2>&1; then
exit 0
fi
---------------------------------------------------------
2. check for panel
---------------------------------------------------------
if qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.dumpCurrentLayout | grep -q “vmhostinfo-panel”; then
exit 0
fi
---------------------------------------------------------
3. generate panel
---------------------------------------------------------
panel=$(qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.createPanel “bottom”)
qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.setPanelVisibility “$panel” “alwaysvisible”
qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.setPanelHeight “$panel” 40
qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.setPanelOpacity “$panel” 0
qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.setPanelName “$panel” “vmhostinfo-panel”
---------------------------------------------------------
4. enhance by text-widget
---------------------------------------------------------
widget=$(qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.addWidget “$panel” “org.kde.plasma.notes”)
---------------------------------------------------------
5. Inhalt setzen
---------------------------------------------------------
DISPLAY_VALUE=$(grep DISPLAY= /run/vm-hostinfo | cut -d= -f2)
IP_VALUE=$(grep IPV4= /run/vm-hostinfo | cut -d= -f2)
qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.setWidgetText “$widget” “$DISPLAY_VALUE ($IP_VALUE)”
EOF
chmod +x /home/vmadmin/.local/bin/create-overlay-panel.sh
chown vmadmin:users /home/vmadmin/.local/bin/create-overlay-panel.sh
cat > /home/vmadmin/.config/autostart/overlay-panel.desktop <<EOF
[Desktop Entry]
Type=Application
Exec=/home/vmadmin/.local/bin/create-overlay-panel.sh
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name=Overlay Panel
EOF
chown vmadmin:users /home/vmadmin/.config/autostart/overlay-panel.desktop
‘’;
}