feat(kasmvnc): bureau distant Plasma 6 en X11 (startplasma-x11 + look-and-feel, disableBasicAuth)

KasmVNC etant un serveur X, Plasma tourne en session X11 (startplasma-x11) avec une arborescence de donnees isolee (plasmaDataEnv + plasmaConfigDir) exportee uniquement dans l'unite, sans toucher a la session niri locale de marc.

- desktop = "plasma" : xstartup startplasma-x11, look-and-feel org.hermes.kasm.desktop (konsole + LibreWolf epingues)
- disableBasicAuth : le SSO Pangolin gate deja kasm.marcpartensky.com, fin du double login kasmvncpasswd
- setxkbmap ajoute au PATH (kded6 configure les claviers via ce binaire)
This commit is contained in:
Hermes Agent
2026-10-11 19:32:46 -04:00
parent 9ec071dd67
commit d8a993274f
5 changed files with 396 additions and 16 deletions
+262 -11
View File
@@ -137,6 +137,34 @@ let
xstartup = pkgs.writeShellScript "kasmvnc-xstartup" (
if cfg.desktop == "custom" then cfg.customCommand
else if cfg.desktop == "xfce" then "exec ${pkgs.xfce.xfce4-session}/bin/xfce4-session"
else if cfg.desktop == "plasma" then ''
# startplasma-x11 est le point d'entree d'une session KDE : il pose le bus
# de session, ksmserver, kwin_x11, kded, plasmashell. Ne PAS lancer
# plasmashell a la main : la session serait sans gestionnaire de fenetres.
#
# Le PATH de l'unite est court (xauth, xkbcomp, ...). startplasma-x11
# appelle kwin_x11 et plasmashell par leur nom : sans le profil systeme il
# sort en « kwin_x11: not found » et la session meurt immediatement.
export PATH="/run/current-system/sw/bin:${pkgs.kdePackages.plasma-workspace}/bin:$PATH"
export XDG_SESSION_TYPE=x11
export XDG_CURRENT_DESKTOP=KDE
export XDG_SESSION_DESKTOP=KDE
export DESKTOP_SESSION=plasma
export KDE_FULL_SESSION=true
export KDE_SESSION_VERSION=6
export QT_QPA_PLATFORM=xcb
export GDK_BACKEND=x11
# startplasma-x11 ne cree un bus de session QUE si
# DBUS_SESSION_BUS_ADDRESS est vide (plasma-dbus-run-session-if-needed).
# L'unite pointe sur %t/bus : si ce socket n'existe pas (pas de dbus.socket
# utilisateur actif), KDE se contente du pointeur mort et meurt sur
# « Failed to connect to socket ». On le vide dans ce cas seulement.
bus="''${DBUS_SESSION_BUS_ADDRESS#unix:path=}"
if [ -n "$DBUS_SESSION_BUS_ADDRESS" ] && [ ! -S "$bus" ]; then
unset DBUS_SESSION_BUS_ADDRESS
fi
exec ${pkgs.kdePackages.plasma-workspace}/bin/startplasma-x11
''
else ''
export PATH="/run/current-system/sw/bin:${pkgs.lxqt.lxqt-session}/bin:$PATH"
export XDG_SESSION_TYPE=x11
@@ -170,6 +198,185 @@ let
pcmanfm-qt
];
# ---------------------------------------------------------------------------
# PLASMA 6 (desktop = "plasma") : bureau distant complet, en X11.
#
# KasmVNC ne sait pas heberger de session Wayland (Xkasmvnc EST un serveur X ;
# kasmtech/KasmVNC#193 : faire tourner un DE Wayland dedans exigerait de
# patcher KWin). Le mode Wayland de l'option `desktop` n'existe donc pas :
# Plasma tourne en session X11 (startplasma-x11), ce qui est pleinement
# supporte par Plasma 6 et suffisant pour le but (bureau cliquable, terminal,
# navigateur, barre des taches).
#
# La liste des paquets est celle de nixos/modules/services/desktop-managers/
# plasma6.nix (nixpkgs 26.05), source autorisee de « ce qu'une session Plasma
# attend ». Ce module n'est PAS active (services.desktopManager.plasma6) : il
# est global (fonts.fontconfig.defaultFonts, xdg.portal.configPackages,
# qt.enable, programs.kde-pim, orca...) et modifierait la session niri locale
# de marc. Tout est donc confine ici + au XDG_* de l'unite kasmvnc.
#
# ECARTES VOLONTAIREMENT du lot plasma6 (chaque paquet ajoute ici pose ses
# unites systemd dans /run/current-system/sw/share/systemd/user, que le
# gestionnaire utilisateur de marc partage avec sa session niri) :
# baloo, baloo-widgets, kunifiedpush : leurs unites sont
# WantedBy=graphical-session.target, donc elles demarreraient AUSSI dans
# niri (indexeur de fichiers + distributeur de notifications push). Verifie
# dans les .service livres. L'indexation baloo est de toute facon coupee
# par le baloofilerc de plasmaConfigDir.
# drkonqi : pose un gestionnaire de crashs SYSTEME
# (drkonqi-coredump-processor@) qui remonte les core dumps de marc dans sa
# session locale. Sans rapport avec un bureau distant.
# kwallet-pam : exige une integration PAM (security.pam.services.*.kwallet)
# qu'une session KasmVNC n'a pas (aucun login PAM) : son unite
# plasma-kwallet-pam.service (Wants de plasma-workspace.target) echouerait.
# ---------------------------------------------------------------------------
plasmaLookAndFeel = pkgs.runCommand "plasma-look-and-feel-hermes-kasm" { } ''
dir=$out/share/plasma/look-and-feel/org.hermes.kasm.desktop
install -d $dir/contents/layouts
install -m 444 ${./plasma/metadata.json} $dir/metadata.json
install -m 444 ${./plasma/desktop-layout.js} $dir/contents/layouts/org.kde.plasma.desktop-layout.js
install -m 444 ${./plasma/defaults} $dir/contents/defaults
'';
plasmaPkgs =
with pkgs.kdePackages;
[
# Frameworks dont le profil systeme a besoin pour ses greffons Qt
qtwayland qtsvg frameworkintegration kauth kcoreaddons kded kfilemetadata
kguiaddons kiconthemes kimageformats qtimageformats kio kio-admin
kio-extras kio-fuse knighttime kpackage kservice kwallet kwalletmanager
plasma-activities solid phonon-vlc
# Coeur Plasma
kwin
kwin-x11 # LE gestionnaire de fenetres d'une session X11 (kwin seul = Wayland)
kscreen
libkscreen
kscreenlocker
kactivitymanagerd
kde-cli-tools
kglobalacceld
kwrited
milou
kdegraphics-thumbnailers
polkit-kde-agent-1
plasma-desktop
plasma-workspace
kde-inotify-survey
# Integration applicative
libplasma
plasma-integration
kde-gtk-config
# Habillage
breeze
breeze-icons
breeze-gtk
ocean-sound-theme
qqc2-breeze-style
qqc2-desktop-style
# Extras + reglages
kdeplasma-addons
plasma-workspace-wallpapers
kmenuedit
kinfocenter
plasma-systemmonitor
ksystemstats
libksysguard
systemsettings
kcmutils
ksshaskpass
# Applications : le terminal et le navigateur demandes, plus le minimum
konsole
dolphin
dolphin-plugins
ark
spectacle
kate
ktexteditor
gwenview
kcalc
plasma-browser-integration
# Sessions et portails
xdg-desktop-portal-kde
kwayland-integration
plasma5support
kdecoration
kirigami
(lib.getBin qttools) # qdbus, attendu par plusieurs scripts KDE
plasmaLookAndFeel
]
# powerdevil n'est PAS installe : tower a powerManagement.enable = false,
# donc pas d'upower, et surtout AUCUNE extinction ni mise en veille de
# l'ecran pendant une session distante (c'est la raison de fond).
++ lib.optional config.networking.networkmanager.enable plasma-nm
++ lib.optional
(config.services.pipewire.pulse.enable || config.services.pulseaudio.enable)
plasma-pa
++ [
# Le navigateur demande, et les deux paquets que le module plasma6 de
# nixpkgs met dans systemPackages pour l'integration bureau.
pkgs.librewolf
pkgs.hicolor-icon-theme
pkgs.xdg-user-dirs
pkgs.xdg-utils
];
# Arborescence de DONNEES Plasma, isolee du profil systeme. Sans elle,
# plasmashell ne trouve rien : le profil de tower ne lie pas share/plasma,
# share/kservices6, share/kglobalaccel, share/kpackage, share/solid... (le
# module plasma6 de nixpkgs regle ca avec un environment.pathsToLink =
# [ "/share" ] GLOBAL, hors de question sur une machine dont le bureau local
# est niri). On exporte donc ce dossier dans le XDG_DATA_DIRS de la seule
# unite kasmvnc : la session niri n'y voit rien.
plasmaDataEnv = pkgs.buildEnv {
name = "kasmvnc-plasma-data";
paths = plasmaPkgs;
pathsToLink = [ "/share" "/lib/qt-6" "/libexec" "/etc/xdg" ];
ignoreCollisions = true;
};
# Reglages KDE par DEFAUT, exportes dans le XDG_CONFIG_DIRS de la meme unite
# (la session niri ne les voit donc pas non plus). Sans kscreenlockerrc,
# l'ecran distant se verrouille au bout de 5 min d'inactivite, ce qui rend le
# bureau inutilisable a distance.
plasmaConfigDir = pkgs.linkFarm "kasmvnc-plasma-xdg" [
{
name = "kdeglobals";
path = pkgs.writeText "kasmvnc-kdeglobals" ''
[KDE]
LookAndFeelPackage=org.hermes.kasm.desktop
'';
}
{
name = "kscreenlockerrc";
path = pkgs.writeText "kasmvnc-kscreenlockerrc" ''
[Daemon]
Autolock=false
LockOnResume=false
'';
}
{
name = "baloofilerc";
path = pkgs.writeText "kasmvnc-baloofilerc" ''
[Basic Settings]
Indexing-Enabled=false
'';
}
{
name = "powermanagementprofilesrc";
path = pkgs.writeText "kasmvnc-powermanagementprofilesrc" ''
[AC][DimDisplay]
idleTime=0
[AC][SuspendSession]
idleTime=0
suspendType=0
lockScreen=false
'';
}
];
desktopPkgs = if cfg.desktop == "plasma" then plasmaPkgs else lxqtPkgs;
# Generate kasmvnc.yaml, au schema REEL de KasmVNC 1.5.0. L'ancien fichier
# imbriquait network/desktop/encoding/gpu/advanced sous un bloc `server:` qui
# n'existe pas a ce niveau : kasmvncserver repondait
@@ -320,7 +527,7 @@ in {
# Desktop environment
desktop = mkOption {
type = types.enum [ "lxqt" "lxqt-wayland" "xfce" "custom" ];
type = types.enum [ "lxqt" "lxqt-wayland" "xfce" "plasma" "custom" ];
default = "lxqt";
description = "Desktop environment to launch (le .deb 1.5.0 ne sait pas lancer de session Wayland : -xstartup -> X11)";
};
@@ -345,6 +552,16 @@ in {
default = "";
description = "Extra YAML config merged into kasmvnc.yaml";
};
# Pas expose dans kasmvnc.yaml (confirme par kasmtech/KasmVNC#284) : seul un
# flag CLI relaye a Xkasmvnc le desactive. A utiliser quand une autre couche
# (SSO Pangolin) gate deja l'acces, sinon n'importe qui qui atteint le port
# entre sans mot de passe.
disableBasicAuth = mkOption {
type = types.bool;
default = false;
description = "Desactive le Basic Auth HTTP propre a KasmVNC (-DisableBasicAuth). A reserver aux deploiements deja gates par un SSO en amont (ex. Pangolin).";
};
};
config = mkIf cfg.enable {
@@ -352,7 +569,7 @@ in {
users.users.${cfg.user}.extraGroups = [ "video" "render" "seat" "ssl-cert" ];
# Packages
environment.systemPackages = lxqtPkgs ++ cfg.extraPackages ++ [ cfg.package ];
environment.systemPackages = desktopPkgs ++ cfg.extraPackages ++ [ cfg.package ];
# Config directory and file
systemd.tmpfiles.rules = [
@@ -396,17 +613,50 @@ in {
# le wrapper perl exige xauth, xkbcomp et hostname sur le PATH, sinon il
# refuse de demarrer avant meme de lire sa config (inetutils fournit
# hostname : « couldn't find "hostname" on your PATH »).
path = [ pkgs.xauth pkgs.xkbcomp pkgs.xrandr pkgs.procps pkgs.inetutils pkgs.which pkgs.util-linux ];
# setxkbmap : kded6 configure les dispositions clavier via ce binaire et
# journalise « Can't find setxkbmap - keyboard layouts won't be
# configured » sans lui.
path = [ pkgs.xauth pkgs.xkbcomp pkgs.xrandr pkgs.setxkbmap pkgs.procps pkgs.inetutils pkgs.which pkgs.util-linux ];
serviceConfig = {
Type = "simple";
Environment = [
"XDG_SESSION_TYPE=x11"
"XDG_CURRENT_DESKTOP=LXQt"
"QT_QPA_PLATFORM=xcb"
"GDK_BACKEND=x11"
"XDG_RUNTIME_DIR=%t"
"DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus"
];
Environment =
[
"XDG_SESSION_TYPE=x11"
"QT_QPA_PLATFORM=xcb"
"GDK_BACKEND=x11"
"XDG_RUNTIME_DIR=%t"
"DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus"
]
++ (
if cfg.desktop == "plasma" then
[
# La session KDE distante vit dans SON arborescence de donnees
# (plasmaDataEnv) et SES reglages par defaut (plasmaConfigDir).
# Le profil systeme de tower ne lie ni share/plasma, ni
# share/kservices6, ni share/kglobalaccel : sans ces XDG_*,
# plasmashell demarre sans applets, sans look-and-feel et sans
# raccourcis clavier. Rien de tout ca ne sort de cette unite.
"XDG_CURRENT_DESKTOP=KDE"
"XDG_SESSION_DESKTOP=KDE"
"DESKTOP_SESSION=plasma"
"KDE_FULL_SESSION=true"
"KDE_SESSION_VERSION=6"
# Le theme de plateforme Qt ne se charge que si le greffon est
# joignable : QT_PLUGIN_PATH est donc restreint a plasmaDataEnv,
# pas au profil systeme (pas de qt.enable global : une variable
# QT_PLUGIN_PATH pointant des greffons Qt6 casserait les
# applications Qt5 de la session niri de marc).
"QT_QPA_PLATFORMTHEME=kde"
"QT_PLUGIN_PATH=${plasmaDataEnv}/lib/qt-6/plugins"
"QML2_IMPORT_PATH=${plasmaDataEnv}/lib/qt-6/qml"
"XDG_DATA_DIRS=${plasmaDataEnv}/share:/run/current-system/sw/share:/nix/var/nix/profiles/default/share"
"XDG_CONFIG_DIRS=${plasmaConfigDir}:/etc/xdg:%h/.config/kdedefaults"
]
else
[
"XDG_CURRENT_DESKTOP=LXQt"
]
);
# -xkbdir : Xkasmvnc compile son keymap depuis /usr/share/X11/xkb
# (absent sur NixOS) et meurt sinon sur « XKB: Failed to compile keymap
# / Could not start Xvnc ». Le wrapper perl relaie les options inconnues
@@ -415,6 +665,7 @@ in {
${cfg.package}/bin/kasmvncserver ${cfg.display} \
-xkbdir ${pkgs.xkeyboard_config}/share/X11/xkb \
-xstartup /etc/kasmvnc/xstartup \
${lib.optionalString cfg.disableBasicAuth "-DisableBasicAuth \\"}
-fg
'';
# une session qui se termine (logout, plantage de LXQt) ne doit pas
+34
View File
@@ -0,0 +1,34 @@
# Defauts du look-and-feel du bureau distant KasmVNC.
#
# Meme role que contents/defaults de org.kde.breeze.desktop (plasma-workspace) :
# ce fichier est applique a la PREMIERE session Plasma et remplit les kdeglobals
# / plasmarc / kwinrc de l'utilisateur. Volontairement minimal : theme Breeze en
# sombre, icones breeze-dark, decorations Breeze, fond d'ecran "Next".
#
# Image=Next : "Next" est l'Id KPlugin du dossier share/wallpapers/Nexus livre
# par kdePackages.plasma-workspace-wallpapers (verifie), pas un nom invente.
[kdeglobals][KDE]
widgetStyle=Breeze
[kdeglobals][General]
ColorScheme=BreezeDark
[kdeglobals][Icons]
Theme=breeze-dark
[plasmarc][Theme]
name=default
[Wallpaper]
Image=Next
[kcminputrc][Mouse]
cursorTheme=breeze_cursors
[kwinrc][org.kde.kdecoration2]
library=org.kde.breeze
theme=Breeze
[KSplash]
Theme=org.kde.Breeze
@@ -0,0 +1,64 @@
// Layout de la barre Plasma du bureau distant (kasm.marcpartensky.com).
//
// Base : share/plasma/layout-templates/org.kde.plasma.desktop.defaultPanel de
// plasma-desktop 6.6.6. Deux ecarts volontaires par rapport a l'upstream :
// 1. la barre est forcee en bas alors que l'upstream laisse ShellCorona
// choisir un bord disponible (en pratique le bas, mais pas garanti) ;
// 2. le gestionnaire de taches epingle konsole et LibreWolf, pour qu'ils se
// lancent en UN clic depuis la barre et pas seulement via le menu.
//
// Ce fichier est charge par plasmashell au PREMIER demarrage d'une session,
// quand aucun ~/.config/plasma-org.kde.plasma.desktop-appletsrc n'existe. Un
// appletsrc deja present (marc aurait deja lance Plasma sur tower) n'est PAS
// ecrase : c'est voulu, on ne detruit pas un reglage utilisateur.
var panel = new Panel
panel.location = "bottom"
// Hauteur : *2 est trop petit, *3 trop grand (calcul de l'upstream, arrondi au
// nombre pair suivant : le widget de taille de barre n'affiche que des pairs).
panel.height = 2 * Math.ceil(gridUnit * 2.5 / 2)
// Sur un ecran ultra-large, la barre ne s'etale pas sur toute la largeur.
var panelScreen = panel.screen
const maximumAspectRatio = 21 / 9;
if (panel.formFactor === "horizontal") {
const geo = screenGeometry(panelScreen);
const maximumWidth = Math.ceil(geo.height * maximumAspectRatio);
if (geo.width > maximumWidth) {
panel.alignment = "center";
panel.minimumLength = maximumWidth;
panel.maximumLength = maximumWidth;
}
}
panel.addWidget("org.kde.plasma.kickoff")
panel.addWidget("org.kde.plasma.icontasks")
panel.addWidget("org.kde.plasma.marginsseparator")
panel.addWidget("org.kde.plasma.systemtray")
panel.addWidget("org.kde.plasma.digitalclock")
panel.addWidget("org.kde.plasma.showdesktop")
// Epinglage des lanceurs dans le gestionnaire de taches.
//
// Ids de .desktop VERIFIES dans le store : le terminal de KDE 6 s'appelle
// org.kde.konsole.desktop (pas konsole.desktop) et LibreWolf librewolf.desktop.
// Un id inexistant est simplement ignore par l'applet.
//
// try/catch obligatoire : une exception non rattrapee ferait echouer TOUT le
// script de layout, donc une session sans barre du tout. L'epinglage est un
// confort, la barre est l'essentiel.
try {
var tasks = panel.widgets("org.kde.plasma.icontasks")
if (tasks.length > 0) {
tasks[0].currentConfigGroup = ["General"]
tasks[0].writeConfig("launchers", "applications:org.kde.konsole.desktop,applications:librewolf.desktop")
}
} catch (e) {
print("kasmvnc-plasma: epinglage des lanceurs impossible (" + e + ")")
}
var desktopsArray = desktopsForActivity(currentActivity());
for (var j = 0; j < desktopsArray.length; j++) {
desktopsArray[j].wallpaperPlugin = 'org.kde.image';
}
@@ -0,0 +1,20 @@
{
"KPackageStructure": "Plasma/LookAndFeel",
"KPlugin": {
"Authors": [
{
"Name": "Hermes Agent"
}
],
"Category": "",
"Description": "Bureau distant KasmVNC : barre en bas, konsole et LibreWolf epingles",
"Description[fr]": "Bureau distant KasmVNC : barre en bas, konsole et LibreWolf epingles",
"Id": "org.hermes.kasm.desktop",
"License": "MIT",
"Name": "Kasm (Hermes)",
"Name[fr]": "Kasm (Hermes)",
"Version": "1.0",
"Website": "https://marcpartensky.com"
},
"X-Plasma-APIVersion": "2"
}
+16 -5
View File
@@ -111,7 +111,7 @@ in {
};
# ========================================================================
# KASMVNC - Bureau personnel Wayland haute perf (LXQt + kwin_wayland)
# KASMVNC - Bureau distant Plasma 6 (X11)
# ========================================================================
services.kasmvnc = {
enable = true;
@@ -127,7 +127,17 @@ in {
keyFile = "/run/secrets/kasmvnc-key-pem";
};
passwordFile = "/home/marc/.kasmpasswd";
desktop = "lxqt";
# Plasma 6, PAS en Wayland : KasmVNC est un serveur X (Xkasmvnc) et ne peut
# heberger aucun DE Wayland (kasmtech/KasmVNC#193 — il faudrait patcher KWin).
# startplasma-x11 fournit la session X11 complete : barre des taches en bas
# (look-and-feel org.hermes.kasm.desktop, konsole + LibreWolf epingles),
# KRunner, Dolphin, System Settings.
desktop = "plasma";
# Pangolin (auth.sso-enabled = true sur la ressource kasm plus bas) gate deja
# kasm.marcpartensky.com : sans ca, apres le SSO Pangolin, KasmVNC redemandait
# SON propre Basic Auth (mot de passe genere par kasmvncpasswd), un double
# login que meme vnc.marcpartensky.com (noVNC) n'impose pas.
disableBasicAuth = true;
};
# /home/marc/.kasmpasswd : fichier au format kasmvncpasswd. Attention,
@@ -226,9 +236,10 @@ in {
# VNC natif de Pangolin exigerait un connecteur > 1.13. Meme contrainte de
# schema que services/newt : ce Pangolin attend `proxy-resources` + `protocol`
# (`public-resources`/`mode` sont ignores en silence -> domaine en 404).
# Pas de healthcheck : KasmVNC repond 401 sur / sans authentification, donc un
# healthcheck HTTP marquerait la cible unhealthy et Pangolin la sortirait du
# load-balancer (lecon du healthcheck noVNC dans services/newt).
# Pas de healthcheck : meme avec disableBasicAuth, pas verifie assez pour
# changer cette regle sans retester (lecon du healthcheck noVNC dans
# services/newt : une cible qui semble repondre peut quand meme faire
# echouer le probe Pangolin pour une autre raison).
services.newt.blueprint.proxy-resources.kasm = {
name = "kasm";
protocol = "http";