Skip to content

Latest commit

 

History

History
209 lines (157 loc) · 9.08 KB

linux.md

File metadata and controls

209 lines (157 loc) · 9.08 KB

Linux

What "runs on Linux" means for TST Desk, where the semantics genuinely differ from macOS and Windows, and which of those differences are decisions rather than gaps. Written for someone on an X11 or Wayland session wondering whether a refusal is the product or their desktop.

Most of the daemon is platform-neutral. The places it is not are the same shape as on Windows: an operating-system concept that has no equivalent elsewhere.


1. Data directory

The product data directory on Linux / BSD is $XDG_DATA_HOME/tst-desk, or ~/.local/share/tst-desk when that variable is unset. That is the path docs/configuration.md documents and the path both the host and user_data_dir() now join.

The Tauri identifier com.thatsimpletech.tstdesk is the bundle id and the keychain service name. It is not the XDG directory name. A host that joined the identifier onto dirs::data_dir() stored GUI sessions and port.json in ~/.local/share/com.thatsimpletech.tstdesk while tst run / tstd used tst-desk. Those two trees are now the same leaf.

If only the reverse-DNS leftover exists, it is renamed once to tst-desk. If both exist, tst-desk wins and the leftover is left alone — two live stores are not merged.

--data-dir moves the session store, the audit database, logs/tstd.log, and config.yaml. With no flag, config.yaml stays in the platform directory above. The OS keychain does not move.


2. Keychain

API keys go through secret-tool (libsecret) over stdin. The service is com.thatsimpletech.tstdesk. Install libsecret-tools and have a Secret Service running (GNOME Keyring, KWallet, or the desktop's equivalent).

A locked collection is keychain_locked, not raw CLI stderr. Unlock the login keyring, then retry Store. There is no Keychain Access app on Linux.

secret-tool is discovered at call time. The Linux backend is selected whenever sys.platform is Linux, even if the binary is missing — the first store or lookup is where that shows up. A missing binary is a typed KeychainError ("install libsecret-tools"), not a crash: setup_state probes every named credential, and a clean guest without libsecret must still handshake and run the keyless local preset.


3. File permissions

sessions.json, port.json, and remote-token are written chmod 0600. Unlike Windows, that is a real restriction here. The containing XDG directory is usually 0700 as well.


4. Computer use

Live desktop computer-use is X11 only (TD-2001). mcp/tst-cu-mcp talks to libX11 / libXrandr / libXtst and captures through Pillow. Policy (kill-switch, expect_window) is the same layer as macOS and Windows.

health decides support from the environment, without opening a Display:

Session health.supported session_type
Native X11 (XDG_SESSION_TYPE=x11) true x11
Wayland false wayland
XWayland DISPLAY on a Wayland session false wayland

X11 has no TCC-style gate. check_permissions says so (no_gate) and names the limits that actually bite: a missing DISPLAY, a missing XTEST extension, or a Wayland session. There is nothing to grant in Settings.

Wayland cannot be served by extending the X11 backend (TD-2002). Capture would be portal ScreenCast over PipeWire; input would be portal RemoteDesktop or libei; foreground_window is not compositor-neutral. Until a later epic, the product refuses rather than driving XWayland clients and ignoring native Wayland apps. expect_window never degrades: if foreground cannot be read, actuation is refused.

The real-display rust ring is the same session-scoped signal as macOS and Windows (TD-3407): it lights on the first computer-use tool of a turn and stays until turn end, cancel, or the kill-switch. Wayland has no ring. Desktop Design-mode hit-test (TD-3406) is observe-only: AT-SPI when the library is present, otherwise the EWMH window under the point on X11. Wayland still has no Design AX path.

The browser computer-use path (Playwright) is not X11-specific.


5. Close vs Quit

Closing the window is not quitting the app (TD-2902).

  • Close (title-bar X) hides the window. tstd keeps running.
  • Quit (menu / palette Quit TST Desk) sends shutdown and reaps the process group.

The host signals that group (SIGTERM, then SIGKILL past the grace) with the kill system call, not the kill command. The procps kill in Ubuntu 24.04 misreads kill -KILL -1234: the signal went to the wrong group, so tstd outlived Quit, or to every process the user owns when the pid started with 1. Builds before this fix (TD-1406) did that on 24.04; 26.04's procps is patched.

A second launch never starts a second host (TD-5024): it hands over to the running one over the D-Bus session bus, which shows its window, and exits. With Keep running on, closing every window in any order leaves the host in the tray (TD-5020); tray Show or a second launch opens a new window. The tray (TD-4703) is Show / New window / Quit; the tooltip counts running sessions. There is no macOS Reopen event on Linux.

The host keeps two logs next to tstd.log: logs/tstd.stderr.log (tstd's raw stderr, owner-only, one .1 kept) and, in release builds, logs/host.log (its own lifecycle: start, spawn, attach, restart, quit; one dated archive).

While hidden, a running session or a parked approval updates the window title (the cheap Linux / Windows badge path). OS notifications still fire.

Restarting for a deploy

Let open turns finish first, so a restart does not cut one (TD-5023):

  1. cd core && uv run tst drain --timeout 120
  2. Replace the AppImage.
  3. Quit TST Desk (tray or menu Quit), then launch the new AppImage. Launching it while the old host runs only raises the old window (TD-5024), so the old build would keep running.

tst drain waits up to --timeout seconds for open turns, then keeps refusing new ones for --hold seconds (default 120) so the restart lands on a quiet daemon. It exits 0 when no turn is still running, 2 when one is, and 1 when no daemon is running; it never starts one. It prints only session ids, each with how it waits: running, paused, waiting on the user (the progress guard or a cap paused it) or waiting for approval.

A paused turn or one parked on an approval never finishes by itself, so by default it does not hold the deploy up (TD-5155): tst drain lists it under "Waiting on the user" and still exits 0. The restart seals it as "Cut off by a restart" with a Continue chip (TD-5018), and any message the daemon had accepted for that session comes back held above the composer, with Send and Remove; nothing is sent by itself. Attached images do not survive the restart: a held message keeps its text and says how many images it lost. Pass --strict to exit 2 for paused and approval turns too, when you would rather wait for the person.

The hold refuses only verbs that start a turn. Continue (resume), Approve and Deny still pass, so a person who clicks one between tst drain returning 0 and the stop sets that turn running again, and the restart then cuts it. Nothing is lost (TD-5018 seals the cut turn and offers Continue), but the turn has to be picked up again. Stop the daemon promptly after tst drain returns, or use --strict when someone may be at the window (TD-5158).

The restarted daemon starts without a hold; tst drain --release lifts it if you call the deploy off. A turn cut anyway is closed as "Not finished" when the daemon comes back (TD-5018), and a second tstd on the same data dir waits up to 10 s for the first to exit before giving up (TD-5022).


6. Things that are not different

  • Loopback binding. 127.0.0.1 on every platform. Prime directive §2.1 has no platform clause.
  • Steering-file writes. AGENTS.md, CLAUDE.md and .tst/rules/** are refused identically everywhere.
  • Manifest and config paths. Written with forward slashes regardless of host.
  • Killing a command. POSIX setsid + killpg is the reference implementation. Windows is the special case (docs/windows.md §4).

7. Building from source

The README Development section lists the apt packages. On Ubuntu 26.04 use libayatana-appindicator3-dev, not libappindicator3-dev: the older name removes network-manager-applet.

For where each of these decisions was made, see the TD-2001, TD-2002, and Linux data-dir entries in DECISIONS.md.


8. Clean-guest smoke

This host has no /dev/kvm, so the Linux "clean VM" is Docker (ubuntu:22.04 for the extracted sidecar, ubuntu:24.04 for dpkg -i plus the windowed host). From a built .deb / AppImage:

core/scripts/smoke_linux_bundle.sh [path-to-deb] [path-to-appimage]

That script proves: no system Python on the sidecar (ldd + empty PATH), dpkg -i + xvfb-run tst-desk writes port.json, a protocol turn against the shipped local preset (loopback mock → fs_write → reply), and AppImage --appimage-extract of the same sidecar. It does not tick the four-platform packaging boxes. macOS and Windows guests use core/scripts/smoke_macos_bundle.sh and core/scripts/smoke_windows_bundle.ps1 (TD-4906). GitHub Actions package.yml is a separate gate.