Skip to content

Remote access

remote.enable lets you take over the session that is physically on the machine's screen.

gewis.servicePc.remote = {
  enable = true;
  passwordFile = config.sops.secrets.rdpPassword.path;
  firewallInterfaces = [ config.services.netbird.clients.netbird.interface ];
};

Connecting

Host is the machine's mesh name and port 3389; the username defaults to the session user (gewis) and the password is whatever passwordFile contains.

The server requires NLA, so a client that has been forced to plain TLS will be turned away with HYBRID_REQUIRED_BY_SERVER. Every normal client negotiates NLA by default.

Use a native client. NetBird's in-browser RDP finishes the TLS exchange, asks you to accept the certificate, and then stops sending: the session hangs until the server gives up minutes later with

[transport_read_layer]: BIO_read returned a system error 110: Connection timed out
[RDP] Network or intentional disconnect, stopping session

The same host over the same mesh works from a real client:

$ xfreerdp /v:<host>:3389 /u:gewis /p:<password>

A log that ends that way therefore says nothing about the machine; try a native client before looking at anything on it.

Reachability

The firewall stays shut by default. Prefer firewallInterfaces, which opens the port only on the named interfaces, normally the NetBird mesh.

Where the password lives

gnome-remote-desktop keeps its password in libsecret, which in practice means the GNOME login keyring. On a service PC that keyring is never unlocked: GDM's autologin PAM stack only unlocks it when a password was actually typed at login, and nobody types one here. Worse, pam_gnome_keyring's auto_start still launches a keyring daemon for the session, without a password, and that daemon owns org.freedesktop.secrets. A second gnome-keyring-daemon --unlock cannot take the name over, so unlocking after the fact does nothing: GNOME starts asking on screen for a keyring password, and every client is refused with

[RDP] Credentials are not set, denying client

on the machine and gkr-pam: no password is available for user earlier in the journal.

So the module runs the keyring daemon itself, as a session service that --replaces the passwordless one and unlocks the login keyring with the same secret passwordFile points at, creating it if it is not there. A separate oneshot deletes ~/.local/share/keyrings before it, once per session: a keyring left behind by an earlier boot, or by the on-screen "set a password" prompt, has a password nobody knows and would lock the session out for good. Nothing of value is in there, because the RDP credential is stored again on every session start. The delete is its own unit, so a restart of the daemon cannot throw away a credential that was already stored. The daemon Requires= it, so a keyring the delete could not clear is never the one the session ends up using.

Restart = "always", because the daemon exits 0 when it loses the race for $XDG_RUNTIME_DIR/keyring: gkd_control_listen() failing makes main() return FALSE. The daemon that pam_gnome_keyring auto_start created for the autologin session is still holding that socket at session start, so the first attempt regularly exits clean and has to be retried, or the keyring is never unlocked at all.

Both keyring units are ordered before graphical-session.target, so the keyring is open before anything in the session can ask it for a secret. That ordering is only worth anything because the daemon gates it. Before= waits for a start job, and a Type=simple start job is finished the moment the process forks, which is well before the daemon has taken org.freedesktop.secrets from the passwordless one. So an ExecStartPost polls the login collection's Locked property on the session bus and holds the unit activating until it reads b false: Before= then waits for the keyring to be unlocked, and Requires= on it in the credential store means the same. Reading a property never raises a gcr prompt, where asking for a secret would, and --auto-start=no keeps the probe itself from ever being what D-Bus-activates a passwordless daemon. The poll gives up after 30s and fails the unit, which is what makes Restart = "always" retry the --replace.

The credential store stays after the target, and keeps its TimeoutStartSec. Ordered before it, a grdctl blocking on a gcr prompt keeps the target's start job pending forever, and then nothing that belongs to the session ever runs: no browser, no gnome-remote-desktop. The symptom is a session that looks half-started, with graphical-session.target reading inactive dead with a pending start job in systemctl --user --machine gewis@ list-units. The keyring units cannot wedge it the same way, because neither ever prompts and both are bounded: if the gate never passes, the unit ends failed and the session still comes up — without RDP, and with the on-screen keyring prompt this whole arrangement exists to avoid.

Storing is a store-and-read-back loop, because grdctl exits 0 having written nothing while the keyring is not ready yet. On the machine, the same read-back is:

$ grdctl status --show-credentials

Run it as the session user (gewis), in that session: grdctl talks to the session bus, so as any other user it reports that user's own empty configuration and an inactive unit. Username: (empty) for gewis means the keyring did not open, and clients are being denied.