QubesOS / qubes-issues

The Qubes OS Project issue tracker
https://www.qubes-os.org/doc/issue-tracking/
541 stars 48 forks source link

Qubes fails to start/activate systemd `graphical-session.target` target #9576

Open adrelanos opened 5 days ago

adrelanos commented 5 days ago

Qubes OS release

R4.2.

Brief summary

The graphical-session.target is missing in Qubes.

This breaks Kicksecure's and Whonix's msgcollector /usr/lib/systemd/user/msgcollector-gui.service.

Steps to reproduce

systemctl --user status graphical-session.target

Expected behavior

Compared to KUbuntu:

systemctl --user status graphical-session.target
● graphical-session.target - Current graphical user session
     Loaded: loaded (/usr/lib/systemd/user/graphical-session.target; static)
     Active: active since Sat 2024-11-09 17:52:48 CST; 2 days ago
       Docs: man:systemd.special(7)

Actual behavior

Qubes Debian Template:

systemctl --user status graphical-session.target
○ graphical-session.target - Current graphical user session
     Loaded: loaded (/usr/lib/systemd/user/graphical-session.target; static)
     Active: inactive (dead)
       Docs: man:systemd.special(7)
marmarek commented 5 days ago

~Interestingly, there is no graphical-session.target at all in Fedora (but it is documented in the systemd.special man page).~ my bad, typo

marmarek commented 5 days ago

This is going to be a bit tricky, as qubes-gui-agent is a system unit, and graphical-session.target is a user unit. I don't think one can bind them across system/user instances boundary. I guess we need a dummy user unit for this...

ArrayBolt3 commented 4 days ago

On my Kubuntu system, I see that /usr/lib/systemd/user/plasma-workspace.target Requires graphical-session.target, BindsTo it, and is Before it. I can test if making and enabling such a service on R4.3 fixes the issue, and make a pull request if so.

marmarek commented 4 days ago

@ArrayBolt3 the thing is, VM has no equivalent of plasma-workspace.target user unit. There is only qubes-gui-agent.service system unit.

ArrayBolt3 commented 4 days ago

@marmarek Right, but all that needs to be done is making some arbitrary dummy unit like you said, and enabling it (at least I think that will work). I wasn't really coming up with any new ideas, just looking for which settings the dummy unit would need in order to do the right thing.

ArrayBolt3 commented 4 days ago

The following works for me in a whonix-workstation-17 template:

[template workstation user ~]% cat /usr/lib/systemd/user/force-graphical-session.service 
[Unit]
Description=get graphical-session.target to work
BindsTo=graphical-session.target
Before=graphical-session.target
Requires=graphical-session.target

[Service]
ExecStart=/usr/bin/true
Type=oneshot
RemainAfterExit=true

[Install]
WantedBy=default.target

Then systemctl --user enable force-graphical-session.service and reboot. graphical-session.target will be active on boot.

[template workstation user ~]% systemctl --user status graphical-session.target
● graphical-session.target - Current graphical user session
     Loaded: loaded (/usr/lib/systemd/user/graphical-session.target; static)
     Active: active since Tue 2024-11-12 21:58:55 UTC; 1min 50s ago
       Docs: man:systemd.special(7)

Nov 12 21:58:55 host systemd[793]: Reached target graphical-session.target - Current graphical user session.

However, this does NOT seem to make graphical-session.target work in a whonix-workstation-17-dvm machine. Not sure why yet.

edit 1: clarified that this was a whonix-workstation-17 template edit 2: Switched from WantedBy=basic.target to WantedBy=default.target since that seemed more sensible

marmarek commented 4 days ago

Well, even it that "works", it would be wrong thing to do, as graphical-session.target is supposed to be active when actual graphical session is active, not just always.

ArrayBolt3 commented 4 days ago

Yeah, that's a good point. Further complicating matters is that enabling a user service in a template doesn't seem to enable it in a DispVM, and disabling the user service in the template doesn't disable it in the DispVM either. Not sure what's happening there, I'm on Qubes OS R4.3.

ArrayBolt3 commented 4 days ago

Hmm. The way it works in KDE Plasma appears to be something like this:

So it might be possible for the GUI daemon to do sort of the same thing:

That wouldn't require a dummy systemd unit at all, though it would require some substantial complexity. The potentially tricky parts are going to be figuring out which user to "drop" to, and then doing the D-Bus connection.

ArrayBolt3 commented 4 days ago

er, replace gui-daemon with gui-agent, I had them mixed up.

marmarek commented 4 days ago

The proper place is to plug it into qubes-session somehow - this is what is started under Xorg as the user. But still, not sure how to do it to behave as the documentation says:

   graphical-session.target

This target is active whenever any graphical session is running. It is used to stop user services which only apply to a graphical (X, Wayland, etc.) session when the session is terminated. Such services should have "PartOf=graphical-session.target" in their [Unit] section. A target for a particular session (e. g. gnome-session.target) starts and stops "graphical-session.target" with "BindsTo=graphical-session.target".

So, we need to ensure not only it gets started at proper time, but also it gets stopped when graphical session (qubes-session / qubes-gui-agent) are stopped.

marmarek commented 4 days ago

Or maybe even one step earlier - plug it into qubes-run-xorg to call the whole xinit etc (see final lines of the script) under user unit. But then, monitoring if the X server is still running will get tricky, qubes-run-xorg shouldn't exit before X server exits, and systemctl --user start doesn't normally wait for the unit to stop... (maybe systemctl --user --wait start could be used?)

ArrayBolt3 commented 4 days ago

To me it seems like qubes-session is the equivalent of startplasma-x11 as far as sessions are concerned. My initial instinct would be to put it here: https://github.com/QubesOS/qubes-gui-agent-linux/blob/main/appvm-scripts/usrbin/qubes-session#L40-L49 Have a user systemd unit with the needed BindsTo that calls qsvc qubes-gui-agent, and start that unit using systemctl in qubes-session. When qubes-gui-agent dies, so will the unit (if configured properly), which should take down graphical-session.target with it.

marmarek commented 4 days ago

If going the latter direction, this would need to be done carefully - all the user processes (including most qrexec services, xdg autostart apps etc) are started as children of qubes-session, which means it's sensitive for changes in environment vars, applied limits, cgroups etc...

My initial instinct would be to put it here: https://github.com/QubesOS/qubes-gui-agent-linux/blob/main/appvm-scripts/usrbin/qubes-session#L40-L49

Nope, this part applies only to sys-gui (it starts Xfce or whatever user has configured in sys-gui, which itself runs gui-agent connected to dom0).

ArrayBolt3 commented 4 days ago

Nope, this part applies only to sys-gui (it starts Xfce or whatever user has configured in sys-gui, which itself runs gui-agent connected to dom0).

hmm, I may be severely confused as to the purpose of the qubes-session script then, because I don't see anything else in that script that would launch the GUI agent, and I thought this was the GUI agent launcher each qube ran. edit: or I can't read and just missed the blatant exit that was right there :facepalm:

marmarek commented 4 days ago

the flow is: qubes-gui-agent -> qubes-run-xorg -> xinit -> ... -> Xorg + qubes-session

ArrayBolt3 commented 4 days ago

I think your --wait idea will work. man systemctl's documentation on --wait makes it sound like it will work to block until the unit terminates, and I tested the theory with the following simple systemd user unit:

[Unit]
Description=testabcd
[Service]
Type=exec
ExecStart=/usr/bin/sleep 10
[Install]
WantedBy=multi-user.target

With systemctl --user start testabcd, the command returns instantly. With systemctl --user --wait start testabcd, it hangs for 10 seconds.