351 Commits

Author SHA1 Message Date
Dave Kempe b7bdfea3a0 v1.9.5
Multi-monitor support for SPICE and RDP, plus an "open in new window" action
for address-book entries.

- SPICE multi-channel composite (Windows/PVE multi-QXL topology), main-window
  crop to monitor 0, per-monitor pop-out windows.
- RDP multi-monitor via the Display Control channel (patch 010).
- Open a connection in its own browser window from the address book.
- Client: fit-to-window on connect, and pop-outs close/notify with the session.
v1.9.5
2026-07-28 09:46:40 +10:00
Dave Kempe 8d1f6c24d7 fix(multimon): correct SPICE layout when the guest rearranges a secondary head
For a multi-channel SPICE guest (each head is a separate display channel, e.g.
a Windows multi-QXL guest), publish_layout positioned each monitor at
origin_x + the guest-reported config->x/y. But config->x/y is the guest's own
virtual-desktop arrangement, not an offset within the channel's surface: once
the guest rearranges a secondary head (which happens as soon as the primary is
resized, e.g. by fit-on-connect), it reports config->x = primary width, so the
monitor was placed at origin_x + config->x (double-counted) and clamped to a
sliver off-canvas. This is exactly where the compositor already blits the
surface (at origin_x), so publish each channel's whole surface at its origin
and ignore the guest x/y. Single-channel guests (one combined surface with
several monitor regions) still split by the reported regions.
2026-07-28 09:46:40 +10:00
Dave Kempe 928c6d4862 fix(client): fit session to window on connect + tear down pop-outs with the session
fit-on-connect: a session opened in a new window (at a fixed size), or otherwise
created at dimensions that differ from the viewing window, now fits the window
without a manual resize. The client previously only sent a size on a
window-resize event. The one-time fit is retried past the point where the resize
path becomes ready (RDP Display Control channel connected / SPICE agent and
display ready) because an early send is silently dropped; guacd coalesces the
retries into a single resize since the requested size is unchanged.

pop-out teardown: a monitor pop-out is only a satellite of the main window (it
blits from that window's canvas and sends input through its client), so it
cannot outlive it. Closing the main window now closes its pop-outs instead of
leaving a frozen orphan, and a session disconnect shows a "Disconnected" overlay
in the pop-out rather than a stale live image.
2026-07-28 09:15:27 +10:00
Dave Kempe 6cfec88f25 feat(multimon): RDP multi-monitor via the Display Control channel
Adds RDP multi-monitor, reusing the protocol-agnostic client work already used
by SPICE (secondary-monitors advertisement, multimon-layout cropping, pop-out
windows, native-mouse mapping).

guacd (patch 010): a "secondary-monitors" arg enables multi-monitor. The
Display Update module now tracks a per-monitor layout (tiled left-to-right,
top-aligned, widths rounded to RDP-valid geometry) and sends the full
DISPLAY_CONTROL_MONITOR_LAYOUT array via SendMonitorLayout rather than a single
monitor. The host extends its desktop across the monitors and streams one
combined framebuffer, so there is no client-side compositing (unlike SPICE).
guacd advertises secondary-monitors on user join and publishes the
multimon-layout so the client can split the framebuffer into per-monitor
windows.

rustguac: RdpParams gains secondary_monitors, wired from the entry's
max_monitors and sent as the secondary-monitors connect arg (resize-method is
already display-update, which the Display Control channel requires). The
address-book entry editor gains a Monitors field for RDP.

Validated against a Windows RDP host: ticking a second monitor extends the
desktop, the second head renders and resizes, and clicks land on both.
2026-07-28 08:42:23 +10:00
Dave Kempe 203d7c2cca feat(connections): "open in new window" action for address-book entries
Each entry gains a compact icon button beside Connect that launches the
session in a separate browser window (which can be dragged to another
physical display, and its secondary monitors popped out from there). Connect
is unchanged (opens a new tab).

The window is opened synchronously in the click handler so it is not
popup-blocked after the async connect fetch, then navigated once the session
exists; it also works through the credential prompt. The icon is a compact,
vertically-centred sibling that stays on one line with Connect.
2026-07-27 20:40:06 +10:00
Dave Kempe 2be5bfd849 feat(multimon): SPICE multi-channel composite + monitor-0 crop + pop-out input
guacd (patch-008): composite multiple SPICE display channels into one
framebuffer. A Windows/PVE guest exposes each head as a separate QXL device
(a separate display channel with its own primary surface); the previous code
tracked a single display channel and shared surface, so a second head
clobbered it and the session showed black or dropped. Now each channel is
tracked by id, tiled left-to-right into the combined default layer, and the
multimon layout enumerates every head with its offset. The SPICE pointer is
mapped from the combined coordinate to the owning head's local coordinate
(clamped to bounds so an edge value maps to the correct head).

client: the main window now crops to monitor 0 instead of showing the whole
combined framebuffer, and rescales to fit on resize. Secondary monitors open
in their own window that fills and rescales; that window's pointer uses native
mouse events mapped from the canvas's live rect, because Guacamole.Mouse does
not track the X axis correctly in a popup window. The per-frame blit clamps to
the source framebuffer bounds so a resize cannot read past it and produce a
corrupt image.

Validated on a Windows PVE console (VMID 300): both heads render composited,
clicks land on target on both monitors, and resizing the second monitor is
stable.
2026-07-27 20:15:57 +10:00
Dave Kempe 24e8aee5d4 feat(multimon): A3 per-monitor windows + entry storage for monitor count
client.html: per-monitor tick-boxes (contiguity-enforced) that open each
secondary monitor in its own window, blitting that monitor's region of the
combined framebuffer (rAF) and forwarding mouse (combined coords) + keyboard;
untick/close releases the monitor. Requests floor to a sane size if a popup
reports zero dimensions.

Entry storage: AddressBookEntry/EntryInfo gain max_monitors, ab_connect_entry
passes it through, and the Proxmox editor gets a Monitors field. Lets a saved
Proxmox/SPICE entry offer multiple monitors (previously only the ad-hoc API
path could).

KNOWN ISSUE: enabling multi-monitor (secondary-monitors>0) intermittently drops
the SPICE connection ~2-6s after connect, independent of the request. Under
investigation; single-monitor is unaffected. A3 end-to-end (tick -> activate ->
render) still needs a real-browser verification (Playwright can't size popups).
2026-07-27 11:52:31 +10:00
Dave Kempe 1d70b2ae24 feat(multimon): A2 controls sidebar with send-keys (Ctrl+Alt+Del)
Adds a Controls side panel (autohide-integrated, mirroring the clipboard/files
tabs) with a Send Keys section: Ctrl+Alt+Del, Ctrl+Alt+Bksp, Alt+F4, Alt+Tab,
Win, Esc, PrtSc, sent via a press-in-order/release-in-reverse key combo. Also a
Monitors section that reflects the server-advertised count (per-monitor tick-box
selection + rendering land in the next increment).

Verified on the canary vs VMID 300: Ctrl+Alt+Del triggers the Windows secure
attention screen; Monitors shows '2 monitors available'.
2026-07-27 11:19:35 +10:00
Dave Kempe c526c2f00d feat(multimon): A1 receive plumbing for multi-monitor (SPICE)
Client library (Client.js): new onmultimonlayout callback + a multimon-layout
layerPropertyHandler (mirrors multi-touch), and sendSize extended to carry the
optional per-monitor x_position/top_offset.

rustguac: SpiceParams.secondary_monitors + a secondary-monitors connect arg so
guacd advertises the allowed monitor count; CreateSessionRequest.max_monitors
(secondary = max-1) wired through both SPICE branches.

client.html: onargv reads the secondary-monitors count and onmultimonlayout
parses the layout JSON (logging for now; per-monitor windows are A2).

Verified on the canary vs VMID 300: client logs 'server allows 2 monitors' and
receives a multimon-layout. The layout is request-driven (guacd activates a
second guest head only when the client sends a size for monitor 1), so the
second monitor appears in A2.
2026-07-27 11:10:52 +10:00
Dave Kempe 3fb250dc99 docs(readme): add SPICE + Proxmox VE session types and headless API integration
Documents the new SPICE and Proxmox VE console session types (intro, architecture
diagram, and session-type table) and the headless ws-ticket API integration.
Also replaces em-dashes with colons/sentence breaks throughout the README to
match the project prose style.
2026-07-27 08:00:00 +10:00
Dave Kempe 6d1197fdc3 docs(api): document session connection flow, ws-ticket, and SPICE/Proxmox
Adds a 'Connecting to a session' section covering the owner-vs-join
distinction, the three owner-auth modes (OIDC cookie, sessionStorage key,
ws-ticket URL), POST /api/ws-ticket, and the end-to-end headless integration
recipe (mint a ticket, open /client/{id}?ticket=...). Documents the new
spice/proxmox/vdi session types and their spice_*/proxmox_* fields, and fixes
the stale share_url response example (was &key=; now ?token= with ws_url/status).
2026-07-26 08:51:24 +10:00
Dave Kempe 86cee24e63 fix(fuzz): bump russh 0.62.2 -> 0.62.4 in the fuzz lockfile
Clears Dependabot alerts GHSA-g9hv-x236-4qp3, GHSA-cqjc-rmpq-xprq,
GHSA-5xvq-cp9x-6p6r (russh pre/post-auth panics, patched in 0.62.4). Only the
fuzz harness's lockfile was affected; the shipped binary already uses russh
0.62.4 via the main lockfile, so v1.9.0 is not vulnerable.
2026-07-26 08:32:01 +10:00
Dave Kempe c675f250f5 v1.9.0 v1.9.0 2026-07-26 08:15:25 +10:00
Dave Kempe 7d9350e065 feat(client): accept a ws-ticket in the client URL for headless owner connections
The built-in client could only authenticate the owner WebSocket via an OIDC
session cookie or a sessionStorage API key. Headless API integrations have
neither, so the owner connection was rejected and guacd reported 'User is not
responding'. Now /client/{id}?ticket=<wst> is honoured: a backend mints a
single-use ticket via POST /api/ws-ticket and hands the browser a ready URL,
keeping the durable API key server-side. The metadata fetch is skipped in this
path (it needs its own auth and would consume the one-shot ticket).
2026-07-26 08:06:49 +10:00
Dave Kempe edd7d7d377 deps: bump russh 0.62.4, base64 0.23, clap 4.6.3, tokio 1.53.1, serde_json 1.0.151, tokio-util 0.7.19, libc 0.2.189
Clears dependabot #182-#188. base64 needed the direct constraint widened to
0.23 (still used for JWT decode in oidc.rs); russh 0.62.4 moves curve25519/
ed25519-dalek off release candidates onto stable. 279 tests green, clippy clean.
2026-07-26 07:22:55 +10:00
Dave Kempe cd9117936d fix(guacd): silence spice-gtk 'Invalid port value' on TLS-only SPICE (patch 009)
For TLS-only SPICE (Proxmox), rustguac sends an empty plain port so guacd
uses tls-port. guac_spice_session_configure() set the spice-gtk port property
for any non-NULL settings->port, but an omitted arg parses to an empty string,
so spice-gtk logged 'Invalid port value' per channel. Only set the port when
non-empty.
2026-07-24 19:42:23 +10:00
Dave Kempe 00a8f42572 feat(spice): Proxmox VE console connection type (node auto-detect, split token, SSH tunneling)
Split SPICE into two connection types: "spice" (direct libvirt/QEMU) and
"proxmox" (PVE console brokered via the spiceproxy API). Both produce a guacd
SPICE connection.

- Deliver the SPICE ticket/password as a connect arg instead of a post-connect
  argv stream, so it is set before guacd authenticates. Fixes an auth race that
  produced intermittent "SPICE authentication failed".
- TLS-only SPICE sends an empty plain port so guacd connects via tls-port
  rather than plaintext against a TLS endpoint.
- Proxmox node is optional: resolve it from the VM id via /cluster/resources
  (as the PVE web UI does).
- Split the PVE API token into a visible Token ID (shown in the User column)
  and a masked secret; join them as "id=secret" for the API.
- Surface the PVE response body on non-2xx (safe: only a 2xx spiceproxy
  response carries a ticket), turning opaque 500s into actionable messages.
- SSH tunneling for Proxmox: tunnel both the PVE API call and the spiceproxy
  connection through the jump-host chain in-branch. Also rewrite tls_port
  (not port) for direct-SPICE TLS over a tunnel.
- Store proxmox fields on address book entries; populate Host/User columns;
  orange Proxmox badge. Runtime dep: libspice-client-glib-2.0-8.
2026-07-24 17:42:09 +10:00
Dave Kempe 8c70a23780 feat(spice): Proxmox VE SPICE broker (backend, API-level)
Add a just-in-time Proxmox broker for SPICE consoles. PVE issues one-time,
~30s SPICE tickets via its API, so they cannot be stored; the broker fetches
the config at connect time:
- src/pve.rs: minimal PVE API client. POSTs to
  /api2/json/nodes/{node}/qemu/{vmid}/spiceproxy with an API-token header,
  parses host / proxy / tls-port / password(ticket) / ca / host-subject, and
  unescapes the CA PEM newlines. Never logs the token or ticket, and never
  puts the response body (which carries the ticket) in an error.
- session.rs: CreateSessionRequest spice_pve_* fields (host/node/vmid/token/
  verify_tls); when spice_pve_host is set, the SPICE create_session branch
  calls the broker and maps the result onto SpiceParams (hostname=host, plus
  proxy, tls, tls-port, ca-cert, cert-subject, and the argv ticket).

API-testable now (POST /api/sessions with session_type:spice + spice_pve_*).
Address-book entry storage + a Proxmox UI are the next increment.
2026-07-24 12:59:30 +10:00
Dave Kempe 4a2021bd2f feat(spice): connections.html UI for the SPICE connection type
Add SPICE to the connections entry editor: a SPICE type option, a fields
block (hostname / port / password / color-depth, plus TLS / tls-port /
ignore-cert / CA cert / cert-subject / proxy for connecting through a SPICE
proxy such as Proxmox's), and the show/hide, save, load, and clear wiring
mirroring the VNC type. connections.html is served from disk, so no binary
rebuild is needed for this file.
2026-07-24 11:10:06 +10:00
Dave Kempe b0c6958b2d feat(spice): add SPICE session type + rustguac plumbing
Wire SPICE as a first-class session type through the rustguac stack,
mirroring the VNC/RDP pattern:
- guacd.rs: SpiceParams + ConnectionParams::Spice + protocol select + arg
  mapping. SPICE credentials (password/username) are streamed to guacd via an
  argv stream after connect (send_argv), since guacd's SPICE client reads them
  from argv, not the connect args. SPICE has no width/height/dpi connect args
  (it sizes via the size instruction).
- session.rs: SessionType::Spice, CreateSessionRequest spice_* fields
  (tls/tls-port/ca-cert/cert-subject/proxy), a SPICE create_session branch,
  and tunnel host/port handling.
- vault.rs/api.rs/import.rs: AddressBookEntry + EntryInfo spice_* fields
  threaded through the connect / quick-connect / import paths.

The tls/ca-cert/cert-subject/proxy fields lay groundwork for brokered Proxmox
VE consoles. No connections.html UI yet (to follow); usable via the API.
2026-07-24 10:04:05 +10:00
Dave Kempe c2c5679cad build(spice): vendor guacd SPICE protocol support (patch 008)
Vendors native SPICE protocol support (libguac-client-spice) from upstream
PR apache/guacamole-server#688 (GUACAMOLE-261) as patch 008, on top of the
pinned guacd (6719b20d) + existing patches. Wires --with-spice and the
libspice-client-glib-2.0-dev build dep into build-deb.sh, install.sh,
Dockerfile, and dev.sh. guacclip is kept in the source but not built
(--disable-guacclip, like guacenc/guaclog); the PR's incidental non-SPICE
terminal.c keyboard change is excluded.

guacd builds green with libguac-client-spice on Debian 13 under -Werror.
rustguac-side wiring (SessionType::Spice / SpiceParams) still to come.
2026-07-24 08:52:02 +10:00
Dave Kempe 413619857c v1.8.2 v1.8.2 2026-07-23 10:10:55 +10:00
Dave Kempe 39075d8fa3 build(guacd): uplift pinned guacd 1.6.0-218 -> 1.6.0-302 (main 6719b20d)
- Bump pin 2980cf0 -> 6719b20d in Dockerfile, install.sh, release.yml,
  docs/installation.md. -Werror verified clean on the new base (the
  GUACAMOLE-2221 pin reason no longer applies).
- Drop patch 006 (terminal OSC-consume): upstreamed as GUACAMOLE-2213
  (guac_terminal_unknown_osc).
- Rebase patch 004 (H.264 display worker) onto the refactored libguac
  display internals: the queued-H.264-frame free moved into the deferred
  guac_display_free_removed_layers path.
- Patches 001/002/003/005/007 unchanged (apply clean on new base).

Local build green under -Werror (guacd + rdp/ssh/vnc). H.264 passthrough
still needs runtime verification on an xrdp+x264 target.
2026-07-23 09:38:56 +10:00
Dave Kempe 45b6d64ae9 v1.8.1 v1.8.1 2026-07-20 15:19:35 +10:00
Dave Kempe e7c515654e feat(client): per-entry auto-hide for the clipboard/files side tabs
Add an autohide_side_tabs option (Option<bool>, default off) on the address
book entry, threaded through the same path as fullscreen_on_connect
(AddressBookEntry, EntryInfo, CreateSessionRequest, Session, SessionInfo, the
API connect/quick-connect builders, and import defaults). When set, client.html
slides the left-edge Clipboard and Files tabs off screen when idle and brings
them back when the pointer nears the left edge; defaults preserve the current
always-visible behaviour. Checkbox added to the entry editor.
2026-07-20 15:19:35 +10:00
Dave Kempe 8a047c7296 fix(auth): fail closed on unparseable credential expiry
validate_api_key and validate_user_token enforced expires_at only when it
parsed as strict RFC 3339, silently ignoring any other format, so a malformed
value (e.g. "2026-12-31" or the SQLite "YYYY-MM-DD HH:MM:SS" timestamp the DB
itself writes) let the credential authenticate forever. Add parse_expires_at,
which accepts RFC 3339, ISO-without-zone, SQLite datetime and bare dates
(end-of-day UTC), and treat an unparseable value as expired. Reasonable
formats now enforce correctly rather than locking the credential out.
2026-07-20 11:36:59 +10:00
Dave Kempe 48d263b733 fix(api): require ownership for VDI container thumbnails (IDOR)
get_vdi_container_thumbnail served any container's live desktop screenshot
to any authenticated user: it took only the container name and did no
ownership check, and names are the deterministic rustguac-vdi-{user}. Add an
owner-or-admin gate mirroring get_session_thumbnail: a caller may only read a
container derived from their own username (rustguac-vdi-{user}[-{entry}]);
admins may read any. Returns 404 for non-owners so container existence is not
leaked.
2026-07-20 11:28:17 +10:00
Dave Kempe b52fd1df82 ci: bump actions/checkout v6->v7, actions/cache v5->v6 2026-07-20 11:06:34 +10:00
Dave Kempe 0d4eb24994 deps: batch bump for 1.8.1
- quinn-proto 0.11.14 -> 0.11.16 (RUSTSEC-2026-0185, remote memory
  exhaustion via reqwest; unblocks CI cargo-audit)
- russh 0.61 -> 0.62 (also drops the yanked crypto-bigint 0.7.3)
- tower-http 0.6 -> 0.7
- rustls 0.23.42, uuid 1.24.0, socket2 0.6.5, serde_with 3.21.0,
  rand 0.10.2, webpki-roots 1.0.9, toml 1.1.3, cmov 0.5.4
- fuzz lockfile synced (russh, serde_with, cmov)

Clears the open dependabot queue (#164-#180).
2026-07-20 11:03:43 +10:00
pletch 9735cb50d2 feat(rdp): make wallpaper, theming, full-window-drag configurable per entry
These three RDP visual flags were hardcoded off in guacd.rs. Expose them
as per-connection options (Option<bool>, default false) threaded through
the same path as enable_desktop_composition: RdpParams, the session
request, Vault entry + response, the API connect/quick-connect builders,
import defaults, and the connections.html entry editor (Video Performance
section). Defaults preserve existing behavior; VDI sessions stay off.

Cherry-picked from pletch/rustguac@da3cfda
2026-07-12 20:37:20 +10:00
pletch 4bcac32576 fix(rdp): force AVC420 for H.264 passthrough (fixes AVC444 color corruption)
The H.264 passthrough advertised GfxAVC444, so Windows hosts encoded with AVC444,
which splits the image across two bitstreams (luma main view + auxiliary chroma).
The passthrough only forwards bitstream[0], so the browser WebCodecs decoder
rendered a luma+chroma split — two blocks with green and magenta casts. RFX was
unaffected (separate codec path).

Set GfxAVC444 = FALSE in patches/004 (both the FreeRDP3 setter and direct-field
hunks), keeping GfxH264 = TRUE; AVC444v2 is never enabled and defaults off, so the
client now advertises AVC420-only. AVC420 carries a complete YUV420 frame the
decoder handles correctly. Verified against a Windows RDP session. README updated.

Cherry-picked from pletch/rustguac@17213e2
2026-07-12 15:54:30 +10:00
pletch f693b855dd fix(admin): show timestamps in local time instead of UTC
Backend stores timestamps as SQLite datetime('now') (UTC, no zone marker) and the
admin page printed them verbatim, so last-login/created/last-used/audit times read
as GMT. Add a localTime() helper that tags the unzoned string as UTC and renders
toLocaleString(); apply it to all full date-time cells. Date-only token columns
are left as UTC dates (localizing a 23:59:59Z expiry could roll the date a day).

Cherry-picked from pletch/rustguac@b5ea32e
2026-07-12 15:54:30 +10:00
pletch 5446fe9267 perf(net): disable Nagle (TCP_NODELAY) on rustguac's TCP sockets
Both of rustguac's socket hops carry tiny, latency-sensitive writes; under
default settings Nagle coalesces them against delayed-ACK, stalling input
and frame/sync traffic by ~40ms (up to ~200ms):

- rustguac -> guacd (apply_keepalive, covers both connect sites): forwards
  mouse/keyboard input events.
- rustguac -> browser (both TLS and plain listeners): display frames and
  H.264 sync acks. Linux propagates the option to accepted sockets, matching
  how keepalive is already applied here.

socket2 exposes this as set_tcp_nodelay().

Cherry-picked from pletch/rustguac@a11e7a2
2026-07-12 15:37:17 +10:00
pletch e23c189b63 feat(patches): port OSC-consume and RDP mod-16 fixes from fixes-1.6.0
Two guacamole-server patches ported from pletch/guacamole-server
(fixes-1.6.0), verified to apply cleanly on top of 001-005 against the
pinned base (apache/guacamole-server@2980cf0):

- 006-terminal-osc-consume: route unrecognized OSC sequences to the APC
  handler instead of reverting to echo (GUACAMOLE-2213). Fixes garbage
  output from e.g. systemd OSC 3008 context sequences.
- 007-rdp-disp-mod16: round RDP display dimensions down to mod-16 to avoid
  the green band along the bottom edge from H.264 macroblock padding;
  complements 005 (legacy bitmap path) by covering the H.264/GFX path.

The fork's SO_ERROR connect fix (GUACAMOLE-2107) is already in the pinned
base, and its GFX H.264 enablement is already covered by 004.

Cherry-picked from pletch/rustguac@cfe2c2e
2026-07-12 15:37:17 +10:00
Dave Kempe 627c05074b docs(oidc): document outbound HTTP proxy (Squid) egress 2026-07-06 20:12:44 +10:00
Dave Kempe 383aea9926 v1.8.0 v1.8.0 2026-06-17 22:22:47 +10:00
Dave Kempe a18a507bcf feat(recordings): paginate lists + show typescript storage path
The recordings page rendered every recording into one table, which got
unwieldy with a large backlog and pushed the SSH Typescripts section far
down the page. Add client-side pagination (50/page, Prev/Next) to both
the recordings and typescripts lists; it composes with the existing
search and sort, and auto-refresh preserves the active filter + page.

Also surface where typescripts live on disk: /api/typescripts now returns
{path, items} (endpoint is new in this release, so no compatibility
break) and the typescript section shows "Stored at <path> on the rustguac
host" — useful since the content is intentionally not downloadable.
No new endpoint.
2026-06-17 22:22:47 +10:00
Dave Kempe 5774586106 feat(ssh): per-connection typescript opt-in + LUKS-at-rest docs (#159)
Typescript recording is now per-connection opt-in, off by default. Adds a
record_typescript flag on the address-book entry (Vault), threaded through
EntryInfo / CreateSessionRequest, and a "Enable typescript recording for
this session" checkbox in the connection editor's Recording Settings (SSH
entries only). The SSH branch records a typescript only when the entry has
opted in AND [recording].typescript_path is configured globally. Ad-hoc
SSH sessions (no entry) never record.

Docs: document the per-connection opt-in, and add a LUKS-at-rest recipe
(point typescript_path at a subdir of the LUKS-encrypted drive volume
rustguac already mounts) as the recommended way to encrypt typescripts at
rest with no extra infrastructure.
2026-06-17 21:52:23 +10:00
Dave Kempe 718010b2c0 deps: bump rusqlite 0.40.1, russh 0.61.2, uuid 1.23.3
russh also synced in /fuzz. Closes #160, #161, #162.
2026-06-17 21:18:27 +10:00
Dave Kempe d4999a1865 feat(recordings): list-only typescript register (#159)
Add GET /api/typescripts (poweruser+) and an "SSH Typescripts" section on
the recordings page. List-only by design: it shows that a session was
recorded (name, size, time) but never serves or downloads the content.

Typescripts capture full terminal output, which can include passwords
typed at prompts or secrets printed to screen, so exposing the text via
the web UI would widen its blast radius. A poweruser gets accountability
(a session was recorded) while retrieving the actual log still requires
direct access to the rustguac host or storage. There is deliberately no
serve or delete endpoint, hence no name parameter and no path-traversal
surface. The .timing sidecar is filtered out so one row == one session.
2026-06-17 21:13:53 +10:00
Dave Kempe 3e1dcccb83 feat(ssh): typescript recording passthrough (#159)
Expose guacd's SSH typescript recording via a [recording] config block:
typescript_path / typescript_name / create_typescript_path. When
typescript_path is set, guacd writes a plain-text log of the full
terminal session (scriptreplay-compatible, greppable) for every SSH
session. Aimed at audit/compliance on network gear.

guacd does not template the typescript filename (it uses the name
verbatim and only appends a numeric suffix to avoid clobbering), so
rustguac expands its own brace tokens before passing the name on:
{user} {connection} {host} {date} {time} {session}. Substituted values
are sanitised to [A-Za-z0-9_-], so OIDC emails and free-text entry
names can't produce path separators or traversal. Default template is
{connection}-{user}-{date}-{time} for identifiable audit filenames.

recording-include-keys (keystroke logging in guacd's graphical
recording, for guaclog) is intentionally not wired up: rustguac records
the proxied stream itself rather than driving guacd-side graphical
recording, so that flag would be a no-op. The typescript is the
supported text-audit path.

Docs in configuration.md. 5 unit tests covering token expansion,
the default template, sanitisation/traversal, empty-value fallback,
and unknown tokens.

Closes #159.
2026-06-17 21:00:33 +10:00
Dave Kempe 8b4ea823b1 v1.7.3 v1.7.3 2026-06-09 16:41:15 +10:00
Dave Kempe 9d23c2fe5f deps: bump uuid 1.23.1 to 1.23.2, chrono 0.4.44 to 0.4.45
Closes #157, #158.
2026-06-09 16:40:47 +10:00
Lindsay Harvey 8f67a668ea fix(client): move fullscreen control into session menu (#156)
The v1.7.2 floating "⛶ Fullscreen" corner button at 0.45 opacity was
still 80px of permanent clutter in the top-right of the remote session
display before fullscreen was entered. This PR moves the manual
fullscreen action into the existing Ctrl+Alt+Shift session-menu panel
(next to the Home button), removing the floating overlay entirely.

The per-entry `fullscreen_on_connect` flag and the in-fullscreen top
bar (entry name + Exit + Disconnect) are unchanged. Esc-key forwarding
via navigator.keyboard.lock still applies.

Also adds an "In-session keyboard shortcuts" section to
docs/web-sessions.md documenting the Ctrl+Alt+Shift panel toggle,
Ctrl+V clipboard paste-sync, Esc behaviour, and the disable_copy /
disable_paste interaction.

Closes #156.
2026-06-09 16:25:25 +10:00
Dave Kempe 0a6b27cc4b docs(installation): document non-Debian-13 install paths
Add 'Other Linux distributions' section explaining the FreeRDP ABI
mismatch that breaks drive/audio when running the Debian 13 .deb on
Ubuntu 24.04 (and likely other distros). Recommend the Docker image as
the supported path; provide an untested build-from-source recipe for
Ubuntu 24.04 against system FreeRDP 3.5. Mirror the pointer from
deployment-guide.md. Prompted by #153.
2026-06-04 13:26:14 +10:00
Dave Kempe d02a9cffe0 v1.7.2 v1.7.2 2026-06-04 11:46:42 +10:00
Dave Kempe 5cb7804346 deps: bump russh in /fuzz to 0.61.1 (matches main tree)
Closes #155.
2026-06-04 11:46:42 +10:00
Dave Kempe d6a1adf10f fix(client): auto-hide fullscreen top bar (#154)
The in-fullscreen top bar covered the remote desktop's own menubar
(xfce4 panel, Windows taskbar). Match the mstsc.exe pattern that #154
referenced: show briefly on fullscreen entry, then slide up out of
view. Reveals when the mouse hits the top 4px edge; hides again ~600ms
after the mouse moves below the bar area. Hysteresis between 36 and 48
pixels keeps a jittering pointer from flickering the bar.
2026-06-04 09:30:26 +10:00
Dave Kempe 575d4ab798 docs(integrations): add Microsoft Entra ID setup guide
Covers the claim-vs-scope distinction that trips Entra setups when users
copy the Authentik example. Includes the AADSTS650053 error explainer
plus a troubleshooting section. Promised in #153.
2026-06-03 15:19:57 +10:00
Dave Kempe bd1915fe4f feat(client): per-entry fullscreen-on-connect (#154)
Per-entry boolean fullscreen_on_connect flag. When set, the client enters
browser fullscreen on the first user gesture after CONNECTED and locks
the Escape key (Chromium navigator.keyboard.lock API) so it reaches the
remote session instead of exiting fullscreen. Firefox / Safari fall back
to standard fullscreen with a one-time toast explaining Esc will exit.

A small floating "Fullscreen" toggle in the top-right corner lets any
user enter fullscreen at any time once the session is connected. In
fullscreen mode a thin top bar shows the entry name plus Exit and
Disconnect buttons.

Closes #154.
2026-06-03 15:19:57 +10:00