Repository navigation
Description
Epic: #5412 · Parent design: #5343 · Related: #5509 (dedicated RustDesk SSH forward)
Context
Remote desktop to the laptop today works, but not through the fleet:
- RustDesk (physical desktop) goes over a dedicated raw SSH forward
(~/tools/rustdesk-gl/forward.sh: sandbox127.0.0.1:21118→ laptop
127.0.0.1:21118, its own non-multiplexedssh -N -L, isolated after
RustDesk: login succeeds but no video — PipeWire ScreenCast negotiation stalls under Hyprland (amd-6) #5509 root-caused freezes to contention on the shared laptop-ssh
ControlMaster). It works and sessions run indefinitely, but it is a
second, SSH-shaped transport beside the fleet brokers, with no pooling,
no preconnect, and no place in the fleet services map. - Chrome Remote Desktop (the
amd-crdcontainer on the laptop) gives
a virtual desktop that shares the laptop's filesystem and is decoupled
from the physical desktop login — which matters because laptop desktop
login state has been a recurring blocker for the physical session. It
is Google-brokered and is a separate world from the fleet. - Fleet proxy is a TCP CONNECT proxy:
tunnel.rsopens raw tunnels
through the runtime egress proxy,serve.rspools them per port
(default 50) and pre-warms the laptop-agent pool at startup — but the
broker protocol only carries HTTP request/response JSON for services
(ports 4799/4800/4801 today), never a raw long-lived byte stream. - Sandbox constraint: the sandbox reaches the tailnet only via the
runtime proxy CONNECT — TCP only, no UDP. RustDesk's rendezvous /
hole-punch machinery uses UDP heavily, so any fleet design must work
in a TCP-only, relay/direct shape, not the hole-punch shape.
This issue answers, from RustDesk's actual docs/source: where the
secrets live, whether the broker can relay (and pre-warm) a desktop
transport without holding any of them, what concurrent clients mean on
a RustDesk host, and what per-connection virtual desktops would take.
Upstream sources used throughout:
- RustDesk self-host docs — https://github.com/rustdesk/doc.rustdesk.com/blob/HEAD/content/self-host/_index.en.md
- RustDesk advanced settings (
direct-server,direct-access-port) — https://RustDesk.Com/docs/en/self-host/client-configuration/advanced-settings/ - RustDesk client/host source — https://github.com/rustdesk/rustdesk/blob/master/src/client.rs, https://github.com/rustdesk/rustdesk/blob/master/src/server.rs, https://github.com/rustdesk/rustdesk/blob/master/src/server/connection.rs, https://github.com/rustdesk/rustdesk/blob/master/src/rendezvous_mediator.rs
- RustDesk server source — https://github.com/rustdesk/rustdesk-server/blob/master/src/rendezvous_server.rs (
hbbs), https://github.com/rustdesk/rustdesk-server/blob/master/src/relay_server.rs (hbbr)
Fleet sources are cited as pkgs/fleet/…:line in this repo.
Findings (a) — Secrets, in three separate layers
RustDesk conflates easily; the layers are in fact cleanly separated:
1. Rendezvous / ID registration (who is who). Every host registers
with the ID server hbbs (TCP+UDP 21116): RegisterPeer announces the
peer ID + address; RegisterPk registers the peer's uuid and public
key, and hbbs stores id → (uuid, pk, ip) and hands the pk back out
signed (signed_id_pk) inside punch-hole / relay responses
(rustdesk-server rendezvous_server.rs:396,412,488,596,709).
What a rendezvous server holds: peer IDs, public keys, IPs — and its
own server signing key pair (the key clients pin in Settings →
Network). No session password is ever registered.
2. Transport establishment (how bytes get there). hbbs tries to
hole-punch A↔B (TCP 21116, UDP 21116, plus host-side TCP/UDP/KCP/WebRTC
punches). On failure both sides dial the relay hbbr (TCP 21117) with
RequestRelay { uuid, licence_key }; the first arrival waits in a
PEERS map (30 s pairing window) and the second with the same uuid
is spliced to it, set_raw() on both streams, opaque forwarding from
then on (rustdesk-server relay_server.rs:461–492).
What a relay needs: the pairing uuid (a per-connection nonce issued
via rendezvous), and optionally the server licence_key if hbbr
was started with -k — that key gates relay use, it is not the
session password. A relay needs no peer password, no peer private
key, and does not even need the peer's public key (the pk travels
client→client via the signed rendezvous response, not via hbbr).
3. Session authentication (may this client drive the desktop).
This happens host-side, after transport, inside the connection —
in the controlled host's Connection (rustdesk src/server/connection.rs):
- On open the host immediately sends
Hash { salt, challenge }, the
challenge freshly minted per connection
(Connection::start, salt from the permanent-password config,
challenge: Config::get_auto_password(6);on_opensends the Hash,
connection.rs:545–549, 1510–1531). - The client answers in
LoginRequest.passwordwith the proof
SHA256( SHA256(password + salt) + challenge )
(client:handle_hash/handle_login_from_ui, client.rs:4647ff,
4872ff; host:verify_h1/validate_password_plain,
connection.rs:2523–2560). The password itself is never sent, in
cleartext or otherwise. - Which password is accepted is policy, not protocol:
verification-method= temporary (the rotating one-time password
shown on the host) / permanent / both (default); crossed with
approve-mode= password / click (person at the host approves) /
both (defaultpassword-click) —hbb_common
password_security.rs:42–90. Optional TOTP 2FA on the host gates
authorization after the password proof. - On the rendezvous/relay path the proof additionally travels inside
the encrypted session: the client verifies thehbbs-signed peer
pk (decode_id_pkagainst the pinned server key), generates a fresh
symmetric session key, seals it to the host's ephemeral box pk
(create_symmetric_key_msg), and the stream runs NaCl secretbox
from then on (secure_connection, client.rs:1655ff; host side
identity_handshake, server.rs:262ff). Password verification is
therefore end-to-end: rendezvous sees identity, relay sees
ciphertext, host alone checks the proof.
Direct answer: yes — a TCP transport to a RustDesk host can be
opened and its bytes relayed end-to-end without anyone in the middle
holding, seeing, or being able to check the session password. That
is exactly hbbr's design, and a fleet CONNECT splice inherits it:
the broker needs a target host:port and nothing else. No token file,
no password in broker config, no password in broker logs — the broker
cannot parse the stream it splices.
Two caveats that must be designed around, both from source:
- The direct-IP path is NOT the encrypted path. When the client is
given an IP literal it dialscheck_port(peer, RELAY_PORT + 1)=
21118 directly (client.rs:440–455), has nosigned_id_pk, and
secure_connectionfalls through: it sends an empty message and
proceeds unencrypted. Symmetrically the host's direct listener
callscreate_tcp_connection(…, secure = false, …), so
identity_handshakeis a no-op (rendezvous_mediator.rs
direct_server, ~1354–1424; server.rs:213–260). The hash/challenge
proof still never exposes the password itself, but on this path the
proof (and then the whole desktop stream) has no RustDesk-layer
confidentiality or host authentication — and the proof is a fast
double-SHA256 with no slow KDF, so a captured proof is
offline-brute-forceable against a weak permanent password.
Consequences: (i) on the direct path, transport confidentiality must
come from our tunnel (the runtime-proxy CONNECT path / tailnet),
never from exposing 21118 raw; (ii) the laptop's permanent password
must be long/random, since it is the only thing standing behind the
proof; (iii) this is precisely today'sforward.shshape too — SSH
supplies the encryption RustDesk direct mode lacks. - Fail-open downgrade exists on the relay path too: if the signed
pk is missing/invalid, the client logs and falls back to a
non-secure connection rather than failing closed (client.rs
secure_connection). A fleet relay must therefore be a dumb byte
pipe that never strips or rewrites rendezvous messages; if we ever
run our ownhbbs, pin its key in the clients. (Not in the first
build's path: direct mode has no rendezvous messages at all.)
Findings (b) — Pre-warming: warm the tunnel, not the RustDesk session
Fleet side today:
- The broker already pre-warms:
serve.rs:592–606spawns a thread
that fills the pool forPORT_LAPTOP_AGENTat startup, through the
same checkout accounting as requests.TunnelPoolis per-port,
DEFAULT_POOL_SIZE = 50(serve.rs, pool constants ~L66–L96),
overflow = idle → grow → 300 ms wait → grow anyway. - But pooled conns are wrapped as
HttpConnand spent by
do_requestas HTTP request/response exchanges (serve.rs
Broker::open_conn/do_request); the broker's wire protocol
(serve_session, serve.rs:444ff;client.rshello + JSON frames)
has no raw-stream mode. A desktop needs a new capability, not a
new pool. - Below that layer everything needed already exists:
tunnel.rs:140–193connect_tunnelreturns a rawTcpStreamfrom
a CONNECT and reads the proxy response byte-wise precisely so
payload bytes behind the headers are not eaten — i.e. it was built
splice-safe. One explicit hazard:tune()applies
TUNNEL_READ_TIMEOUT = 660 s(tunnel.rs:20–23, 195–199); a desktop
splice must clear/raise that or long sessions die at 11 minutes —
forward.shsessions demonstrably run indefinitely once unmolested.
RustDesk side — which endpoint, and what a bare pre-connect does:
- On a TCP-only path the pool/splice target is the host's
direct-access port, 21118 —direct-server = Y,
direct-access-portdefault 21118 (RENDEZVOUS_PORT + 2;
rendezvous_mediator.rsget_direct_port/direct_server,
~1344–1424; advanced-settings doc above). Do not confuse it with
hbbs's WebSocket port, which is also numbered 21118 but lives on
the server host, not the controlled laptop. The relay port 21117 is
not a host endpoint at all — it ishbbr's, and relay mode would
require the host to dial out to a fleet-runhbbrand pair by uuid
(option C below), which the direct port makes unnecessary. - A bare TCP connect to 21118 is accepted and the host speaks first:
create_tcp_connection(secure=false → handshake no-op) →
Connection::start→on_openimmediately sendsHash{salt, challenge}and waits. No client protocol bytes are required
first; the Hash simply sits in the socket buffer until a real
client is spliced in and reads it, and the client's first frame (an
emptyMessagefrom its non-securesecure_connectionfallthrough)
is a no-op the host discards. So a pre-opened socket is
protocol-valid to hand over. - What breaks naive pre-warming (pool-50 for RustDesk sockets):
- ~30 s silent-idle death. A connection that says nothing is
closed whenlast_recv_timeexceedsSEC30in the connection
loop (connection.rs:1188–1191; comment at :85–89). The 180 s
LOGIN_GRACE(connection.rs:90) only covers connections that
are talking but not yet authorized. A pre-warmed idle socket
therefore lives < ~30 s and needs constant churn. - Unauthorized-connection caps.
MAX_UNAUTHORIZED_CONNS = 64
globally andMAX_UNAUTHORIZED_CONNS_PER_ADDR = 16
(connection.rs:91–106,admit_unauthorizedat :125ff), counted
before the handshake, evicting the oldest on overflow. All
fleet-forwarded connections appear from one address, so a pool
of 50 is impossible and even ~16 idle pre-connects would crowd
out real connection attempts. - Each pre-connect is a real host-side
Connection: visible in
the host's connection manager, mints a fresh challenge, posts a
connection audit record. Churn is user-visible on the laptop.
- ~30 s silent-idle death. A connection that says nothing is
- Conclusion: the expensive, bursty part of starting a desktop
today is tunnel establishment (CONNECT through the egress proxy;
60 s handshake timeout budget in tunnel.rs:19, and seconds-long
opens under burst per serve.rs's pool comments) — exactly what the
existing pooled/preconnected tunnels already amortize. The
RustDesk-layer connect on an already-warm tunnel is one local RTT
plus the Hash the host sends unprompted. Pre-warm tunnels (have
it), splice on demand (build it); do not pre-warm RustDesk
sockets. If measurement later proves the RustDesk connect itself
matters, a cache of 1–2 direct sockets refreshed every < 20 s is
the maximum sane size — never 50.
Findings (c) — Concurrency: one desktop, many viewers, zero isolation
A RustDesk host is a single controlled desktop with a multi-connection
server bolted on: Server owns one set of shared services (display,
audio, clipboard, input) and a map of Connections (server.rs
new(), ~125–180); every authorized connection subscribes to the same
services and its input lands in the same session. So yes, multiple
clients can be connected simultaneously — all viewing and controlling
the same desktop, with interleaved mouse/keyboard and no locking,
partitioning, or per-client anything. That is a collaboration feature
and a footgun; fleet must not mistake "second connection" for "second
desktop".
Same shape elsewhere, for contrast:
- KRDP (
krdpserver, KDE): aSessionControllerwraps each RDP
connection, but every session captures the same running Plasma
session (Portal or Plasma screencast of that session);
--virtual-monitoradds a virtual output to that same session.
Shared desktop, same as RustDesk. - x11vnc / TigerVNC scraping (
x0tigervncserver): attach to the
live X display:0; every viewer shares it (VNC "shared" is the
natural mode). - TigerVNC virtual (
Xtigervnc/vncserver) and xrdp+sesman: the
different model — the server creates a session per login
(display:N, port5900+N; xrdp's sesman authenticates and starts
a per-user X session, reconnect returns to that same session).
Concurrency there means separate desktops, not shared control.
Findings (d) — A desktop per connection
RustDesk cannot do this. It captures the session that exists —
on Linux, screen capture runs through PipeWire / xdg-desktop-portal
in a logged-in session (and the service can serve the login screen,
which is the part that keeps biting us) — and has no notion of
creating a session per connection. Any per-connection desktop has to
come from a different server or from compositor orchestration under
RustDesk-like tooling:
| Option | Per-connection desktop? | Shares | Notes |
|---|---|---|---|
CRD (amd-crd, running today) |
One virtual X session per host instance — not per connection | Same $HOME/filesystem as the laptop |
Already proves the shape we like: independent of physical login (our recurring blocker), but a second connection shares the one virtual desktop, and a shared Chrome profile dir means browser-instance contention. Google-brokered. |
| xrdp + sesman | Yes — per OS user, concurrent | $HOME if same user; isolate with distinct users + shared data dir |
Mature, any RDP client, TCP 3389 splices through the fleet exactly like 21118 would. X11 sessions only — a shrinking target as Plasma goes Wayland-only. Per-connection browser profiles via separate --user-data-dir. |
| TigerVNC virtual | Yes — per display :N, allocatable on demand |
Same as xrdp | Scriptable (fleet could allocate :N per request), X11-only, VNC protocol (RFB auth is weaker; rely on the fleet tunnel for confidentiality, as with RustDesk direct). |
| Nested/headless Wayland per connection | Yes — one kwin_wayland --virtual (or labwc/sway headless) instance per connection, KRDP or wayvnc inside it |
$HOME; per-instance XDG_RUNTIME_DIR, portal, PipeWire |
The forward-looking option and the only Wayland-native one. KRDP alone does not create login sessions — the fleet would orchestrate compositor instances (e.g. systemd user services) and hand out ports. Heaviest build; also the only one that survives Plasma dropping X11. |
| Separate X/Wayland session per user (display-manager) | Per user, not per connection | Same machine, real sessions | Middle ground: pre-provisioned 1–2 extra users with auto-login sessions, each running its own RustDesk/KRDP; connections-per-desktop still shared (see (c)). |
Sharing data across any of these is the easy half — same filesystem,
explicit browser profile dirs per connection
(chromium --user-data-dir=… per instance avoids profile-lock
contention) — the hard half is session lifecycle: who creates,
resizes, reaps, and logs into each desktop.
Options
- A. Status quo — keep
forward.sh(dedicated SSH forward) as the
desktop path. Works, proven; but permanent second transport, no
fleet pooling/preconnect, no services-map presence, SSH credential
path maintained for one port. - B. Broker raw-splice to the direct port (recommended first
build) — fleet proxy gains a splice mode: sandbox-side listener
on127.0.0.1:21118, each accept checks out a pooled tunnel
(connect_tunnel) to laptop:21118 and copies bytes both ways.
Secrets in broker: none (findings (a)). TCP-only compatible by
construction. Keeps the working RustDesk physical-desktop experience
and today's direct-IP client configuration, swapping only the pipe
under it (SSH → fleet). - C. Fleet-run
hbbs+hbbr, force relay — the "real" RustDesk
topology, and the only one with RustDesk-layer E2E encryption +
host-key verification. Costs: running/pinning a rendezvous+relay,
host registration traffic (21116 TCP/UDP) that our sandbox path
can't carry (servers would have to live laptop-side or on a
UDP-reachable host), force-relay config on both ends, and the
fail-open downgrade caveat to police. Revisit if direct-path
confidentiality ever has to come from RustDesk itself rather than
the tunnel. - D. Replace RustDesk with KRDP/xrdp now — premature: throws away
the working physical-session path to solve the virtual desktop
problem, which is separable and later (findings (d)).
Recommendation — first build (smallest useful step)
Implement B, in this order:
- Register the service: add a
rustdeskentry for port 21118
to the fleet services table (fleet-configConfig::services,
currently 4799/4800/4801) so the broker's routing/hello map knows
the port exists. - Add a raw splice capability to
fleet-proxy(new broker
capability besideProxyServe, negotiated in the hello — proto-0
brokers simply don't offer it):- client verb (name TBD, e.g.
fleet proxy desktop) ensures the
broker, then listens on sandbox127.0.0.1:21118; - per accepted connection:
pool_get(21118)→ bidirectional copy
between the local socket and the tunnel stream; noHttpConn
wrapping, no token file, no request framing; the tunnel is
spent, never returned to the pool; - splice streams must not inherit
TUNNEL_READ_TIMEOUT
(660 s) — clear read/write timeouts on spliced conns
(tunnel.rstune()is HTTP-shaped; make it a caller choice).
- client verb (name TBD, e.g.
- Secrets posture, stated in code comments and docs: the broker
and the splice hold no RustDesk credential of any kind; the
permanent password is typed into the RustDesk client on the
sandbox and its proof travels inside the spliced bytes; laptop
21118 must remain reachable only through the fleet tunnel /
loopback-class paths (direct mode has no transport encryption of
its own — findings (a) caveat), and the laptop permanent password
must be long and random. - Keep
forward.shuntouched as fallback until the splice
proves out; then retire it and the last routine use of the raw SSH
forward with it.
Acceptance for the first build:
- Desktop session starts through the splice with the laptop's
existing direct-IP RustDesk config; start latency measured against
theforward.shbaseline (expect tunnel checkout, not tunnel
establishment, to dominate). - A session idles and works well past 30 minutes (no 660 s tunnel
timeout, no mux-style freeze of the RustDesk: login succeeds but no video — PipeWire ScreenCast negotiation stalls under Hyprland (amd-6) #5509 kind). - Broker logs/config contain no RustDesk password or proof material
(they structurally cannot — verify by inspection). - Two simultaneous splices both land on the same laptop desktop —
observed and documented as the shared-desktop semantics of
findings (c), not treated as a bug.
Explicitly not in the first build: RustDesk-socket pre-warming
(findings (b) — at most a later, measured 1–2 socket / < 20 s-refresh
experiment), fleet-run hbbs/hbbr (option C), and per-connection
virtual desktops (findings (d)).
Next steps
- Land the first build above as its own implementation issue
(services-table entry + splice capability + timeout fix). - Measure splice vs
forward.shconnect latency and long-session
stability; only then decide if a 1–2-socket RustDesk pre-warm
experiment is worth its churn. - Spike (separate design issue): per-connection virtual desktop —
headless Wayland compositor per connection (kwin_wayland --virtual+ KRDP) vs xrdp/TigerVNC-virtual as the X11 stopgap,
with per-connection browser profile dirs; CRD/amd-crdstays
the fallback until the spike proves out. - Decide the end-state for option C (fleet
hbbs/hbbr) if a
future requirement wants RustDesk-layer encryption instead of
tunnel-layer confidentiality.
Activity
dezren39 commentedon Nov 5, 2026
RustDesk password rotation + durability — outcome (2026-11-05, Drew's order)
Diagnosis. RustDesk 1.5.0 on amd is a plain home-manager package with a hand-written user unit — it is not Nix-service-managed (this issue's splice design / KRdp remains the declarative successor). The permanent password lives in
~/.config/rustdesk/RustDesk.tomlalongside the ed25519key_pairthe machine ID derives from. The vault copy (BWS secretrustdesk.amd-6-password) was rejected because imperative config rewrites by fleet lanes on 2026-11-04 churned the stored password field four times without re-syncing the vault (the key pair — and thus the ID — never actually changed that day; the drift was password-only). A separate 2026-11-04 lane also repeatedly used the invalid flag--set-password(working form isrustdesk --password, root, against the running service's IPC).Rotation. Fresh 40-char random password generated on-host, written to BWS
rustdesk.amd-6-password(read-back verified through bothbwsand thefleet secrets getroute, hash-compared in-session; the value appears nowhere in this record), and applied to the running service. Verified by a real connection: the server logged an authenticated session (peer-info exchange) at 01:19 CST on machine ID 101234269.Durability mechanism. PR #6140 (merged,
1686930c) addsnixos/hosts/amd-rustdesk.nix: a boot-time system oneshotrustdesk-password-assertthat fetches the password from the vault in the owning user's context (Bitwarden fallback with the on-disk token; the login-session broker is unreachable from a system service, and the fetch additionally needs HOME and XDG_CONFIG_HOME pinned to the user or the fleet CLI resolves root's config) and applies it viarustdesk --password. The secret value never enters the repo or the Nix store. Boot-only by design — applying a password restarts the RustDesk server component — withsystemctl start rustdesk-password-assertas the manual mid-session re-assert. Activation is pending the next system switch that includes the module.Durability proof (reversible simulation). With a full backup taken first, the identity material was moved aside and the service restarted: RustDesk regenerated its config with a new machine ID (1446872941) and an empty password. The assert script (byte-equivalent to the merged unit's script) re-applied the vault password (
permanent-password updatedin the service log), and the same vault password then authenticated against the regenerated config/ID (server peer-info marker, 02:00 CST). Production state was then restored and re-verified: ID 101234269, rotated password field intact, and a final connection authenticated at 02:06 CST.Open caveat (desktop stack, not credentials). Live video in the later test sessions failed on the host's Wayland screen-capture path ("XDG Desktop Portal unavailable") — authentication completes first; capture is a separate stage.
xdg-desktop-portalhad been running since Nov 4; a restart attempt during this work left it cycling inactivating(start timeouts) — flagged for the remote-GUI/runbook work; it does not affect the credential results above.dezren39 commentedon Nov 5, 2026
Portal / Wayland capture stack: repaired (follow-on from the rotation report above)
The rotation's later test sessions failed at video ("Wayland screen capture failed / XDG Desktop Portal unavailable"). Root cause found and fixed 2026-11-05 ~02:08–02:20 CST — no session restart, RustDesk service and its new password state untouched.
Diagnosis — an orphaned share picker wedges the whole portal stack:
SelectSourcesmadexdg-desktop-portal-hyprlandspawnhyprland-share-pickeron the local screen. On this backend (1.4.1) the picker runs synchronously inside the backend's D-Bus handling: while a pick is pending, the backend answers nothing — verified live twice tonight (org.freedesktop.DBus.Peer.Pingto the backend timed out; the instant the picker process was killed, Ping returned and property reads succeeded).xdg-desktop-portalfrontend start (02:01, 02:03, 02:06 CST) deadlocked during ScreenCast init against the same wedged backend and died on the 90 s start timeout, leavingorg.freedesktop.portal.Desktopwith no owner at all.Fix: killed the orphaned picker (PID 2348020, open ~49 min) → backend immediately responsive →
systemctl --user start xdg-desktop-portal.service→active/running, ownsorg.freedesktop.portal.Desktop.Verification (D-Bus level, no local user):
AvailableSourceTypes=7, AvailableCursorModes=3, version=6; same properties served through the frontend's bus name.CreateSessionthrough the frontend → response code 0 with a session handle;SelectSourcesdrove the backend to spawn the share picker again (full frontend → backend → picker chain alive), then the picker was cleaned up and the stack re-verified un-wedged.Recurrence note for the runbook: on this setup, any unattended portal screencast left sitting at the picker wedges the backend globally. First move if it recurs: look for a stale
hyprland-share-pickerand kill it before restarting anything.Sunshine relevance: none — Sunshine is configured
capture=kms(direct DRM/KMS capture, no portal in the path), so this portal state never gated the Sunshine activation spike either way.dezren39 commentedon Nov 5, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 5, 2026
agent-note:v1 agent=repo-worker-code state=picked-up task=10130 lane=8ac61616-3815-4e7b-b33d-840a3829154a at=2026-11-05T12:58:07Z
Resuming triage after the prior parked handoff. Its progress recorded
session_id null; current GitHub state shows the issue open and no open PR matching #5694. I am checking which merged work addresses this design and what remains.dezren39 commentedon Nov 5, 2026
🤖 Agent note · triaged (task 10130, OpenChamber scheduled lane)
Triage findings (verified fresh 2026-11-05):
session_id null; this lane claimed the task after its ready time, so no active session owner was found.04f78696abfbdf6567f58e623d80520436f002faregisters laptop-agent on 4799 but not RustDesk 21118 (pkgs/fleet/crates/fleet-config/src/lib.rs:27,368); the proxy advertisesProxyServeonly (pkgs/fleet/crates/fleet-proxy/src/serve.rs:488) and applies a 660-second tunnel read timeout (pkgs/fleet/crates/fleet-proxy/src/tunnel.rs:22,196).forward.sh; do not pre-warm RustDesk sockets, runhbbs/hbbr, or fold in per-connection virtual desktops.Disposition: triaged; #5694 stays open for the design’s later measurement and virtual-desktop follow-ups.
dezren39 commentedon Nov 5, 2026
rustdesk-password-assert update: the oneshot's first real activations failed (gens 537/538) \u2014 the vault fetch died instantly on every retry. From a system service, runuser preserves the unit PATH, so the inner sh -c was not found (env: 'sh': No such file or directory), with the fallback's bws unreachable behind it. Fixed declaratively: PR #6241 (unit path += pkgs.bws) + PR #6246 (PATH pinned to bash+coreutils+bws store bins on the runuser env call itself). Gen 539 (2026-11-05 08:31 CST): the assert ran to Result=success in ~1s and re-asserted the vault password. Boot re-assertion is now proven end-to-end. Full detail on #5924.