Releases: marcpope/cortendesk
Release list
CortenDesk 1.0.1
Bug fixes.
Fixed
- Web client: switching monitors moved the picture but not the mouse. The client asked the host to switch displays and then assumed it had worked, discarding the host's reply. That reply carries the origin and size of the display now being captured, and input coordinates are absolute virtual-desktop positions — so the origin is what decides which monitor a click lands on. Clicks were mapped against the display list captured at login, which goes stale as soon as a resolution changes or a monitor is re-arranged, and was wrong outright if the host declined the switch. The client now follows the host's confirmation rather than assuming success.
- Every page title read "Sign In | Laravel".
config/app.phpstill carried the framework's stock default for the application name, and the Docker image sets noAPP_NAME— so that default is what nearly every install ran with. The same value fed the From name on outgoing mail, so invitations and emailed sign-in codes carried it too. Fixed at the config layer, which covers Docker, manual clone and standalone SQLite alike. - Topbar contents sat at the top of the bar instead of centred. Shrinking the topbar items to chips in the 1.0.0 redesign collapsed the row to 42px inside a 70px bar.
- Removed Laravel's stock welcome page, unrouted since the first commit.
Upgrade
No migrations. Upgrading is a tag bump and a restart.
If you set APP_NAME in your environment, nothing changes — your value still wins. If you never set one, your console will start calling itself CortenDesk instead of Laravel.
docker pull marcpope/cortendesk:1.0.1
docker pull ghcr.io/marcpope/cortendesk:1.0.1
Requires a running open-source RustDesk server (hbbs/hbbr). No RustDesk license of any kind is needed.
CortenDesk 1.0.0
First stable release. CortenDesk is a self-hosted web console for the open-source RustDesk server (hbbs/hbbr) — device inventory, address book, groups, users and permissions, strategies, audit logs, API tokens, OIDC single sign-on, and an in-browser remote desktop client.
New
- The web client works over plain HTTP. It previously refused to run without TLS. No configuration — the client detects what the browser allows and picks its path.
- Redesigned web client interface — new top bar, dock, and a slide-out panel for file transfer and session details.
- Decode backpressure. A client that cannot keep up now drops delta frames instead of queueing them, so latency stays bounded rather than drifting silently behind the remote screen.
- Stall recovery. If frames arrive and none are decoded, the client requests a fresh key frame instead of showing a frozen picture.
- Touch gesture support in the web client: tap, long-press, and drag.
Notes on HTTPS
HTTPS is still the better setup and remains recommended:
| HTTPS (or localhost) | Plain HTTP | |
|---|---|---|
| Video | VP8, VP9, H.264, H.265, AV1 via WebCodecs | H.264 via Media Source Extensions |
| Hardware decoding | Yes | Browser's own H.264 pipeline |
| Per-frame statistics | Yes | Not reported |
| Input, file transfer, chat | Yes | Yes |
| Clipboard sync | Yes | Browser-restricted |
The WebSocket URLs must match the page scheme — wss:// on an HTTPS console, ws:// on plain HTTP. A browser rejects ws:// from an HTTPS page as mixed content.
Upgrade
No migrations in this release; upgrading is a tag bump and a restart. If you are coming from before 0.9.0, note that API tokens using --assign --strategy_name need strategy: rw and must be reissued.
docker pull marcpope/cortendesk:1.0.0
docker pull ghcr.io/marcpope/cortendesk:1.0.0
Requires a running open-source RustDesk server (hbbs/hbbr). No RustDesk license of any kind is needed.
CortenDesk 0.9.8
Web client redesign. No migrations — upgrading is a pull and a restart.
Session chrome
- Top bar with device status (online state, user@host, platform), monitor picker, fullscreen, image quality and refresh
- View only — blocks all input to the remote device
- Fit to screen or actual size (1:1 with panning)
- Command bar along the bottom instead of floating over the remote screen, so the remote taskbar stays visible and clickable
Input
- Ctrl / Alt hold buttons for click and key combinations, Del, and a shortcut menu: Ctrl+Alt+Del, Windows key, PrintScreen, Escape, Tab
- Pointer and touch modes — in touch mode, drag moves the cursor, tap clicks, long-press right-clicks
- Type — send text to the remote device as keystrokes, for fields the clipboard cannot reach
Slide-out panel
- Files, Chat and Details tabs, collapsed by default, with edge shortcuts to open any tab directly
- File transfer stacked vertically: your files on top, the remote device below, send and receive between them
- Chat with the remote user, with unread badges when the panel is closed
- Details — codec, resolution, FPS, bitrate, frames dropped, duration, peer version
Reliability
- Video decoding now recovers automatically from a stalled decoder instead of freezing until reconnect
- Latency stays bounded on slower devices rather than drifting further behind the live screen
- The web client reports the console version, so there is no second version number to track
Upgrading
docker compose pull && docker compose up -d
CortenDesk 0.9.7
Bug-fix release. No migrations — upgrading is a pull and a restart.
Fixed
Editor dialogs could not be scrolled. On shorter screens the Add/Edit Strategy dialog ran off the bottom of the window with no way to scroll, putting the save button out of reach — so a strategy could not be created at all. The same applied to the user, group, role, invitation, device and API token dialogs; Strategies was simply the tallest and the first to hit it.
Whitespace in the RustDesk server key. id_ed25519.pub is written without a trailing newline, so a key pasted through an editor or a compose heredoc often picked one up. The key is compared exactly, so an untrimmed key failed in a way indistinguishable from a genuinely wrong one — clients just would not connect. The public key, ID server and relay server are now trimmed on input.
Added
Strategies: Auto update (allow-auto-update) — push the client's auto-update setting from the console. Note the client only exposes this on installed Windows, and on installed macOS that is not a custom build; on Linux, portable installs and custom-branded builds the toggle is hidden, so setting it may have no visible effect.
Strategies: clearer label. "Let the connecting side change this device's settings" is now "Enable remote configuration modification", matching the client's own wording — the option already existed, but nobody searching for the client's term could find it. Thanks to @dragonidium11 for both suggestions (#11).
DemoSeeder — populates an evaluation install with sample devices, users, groups, address books and log history:
php artisan db:seed --class=Database\\Seeders\\DemoSeeder
Documentation
The wiki has been reorganised and expanded: a page per feature, a full settings reference, a command-line reference, reverse-proxy examples for Caddy, Traefik and nginx, a guide to standing up hbbs/hbbr in Docker, and troubleshooting.
README corrections: the SSO fallback description was wrong (password sign-in returns if SSO is disabled or incompletely configured, but not merely because the provider is unreachable — use CORTENDESK_OIDC_DISABLED=true for that), the WebSocket bridge section now distinguishes Docker from manual installs, and the trusted-proxy note explains that getting it wrong can lock users out via the per-address sign-in limiter.
Upgrading
docker compose pull && docker compose up -d
CortenDesk 0.9.6
Fixed
-
Sessions that never stop showing as Active (#10).
A session row was only ever closed by one thing: the client reporting the disconnect to the console. Every other way a session can end — the remote machine rebooting, the network dropping, the client crashing, the service being stopped — skipped that report, so the row stayed open and the console listed the session as Active indefinitely. Reconnecting created a second row, so an interrupted session left one dead entry behind every time, and nothing an operator could do from the console cleared them.
Two mechanisms now close them, because neither covers both failure modes on its own:
- Heartbeat reconciliation. Every device already reports its own list of live incoming connections on each heartbeat, and that list is authoritative. Any session the console has open for a device that the device no longer lists is closed. A short grace window protects a session that has only just started, so a live session is never cut short.
- A five-minute sweep for the case reconciliation cannot see: a device that has stopped heartbeating altogether — powered off, unplugged, or with the service stopped — sends nothing to reconcile against. Sessions belonging to devices that have gone quiet are closed on a timer instead.
Both paths are pinned to the reporting device, so this changes nothing about who can end whose sessions.
Reported by @dragonidium11.
Notes
- No migration and no configuration changes.
- Existing stale rows clear themselves after upgrade — devices still online are reconciled within about 90 seconds, the rest within five minutes. No manual cleanup.
- The five-minute sweep runs on the Laravel scheduler. The Docker image runs the scheduler for you. Manual installs need the scheduler cron (
* * * * * cd /path/to/cortendesk && php artisan schedule:run >> /dev/null 2>&1) for that half to work — heartbeat reconciliation works either way. It can also be run by hand withphp artisan cortendesk:close-stale-sessions.
docker pull marcpope/cortendesk:0.9.6
docker pull ghcr.io/marcpope/cortendesk:0.9.6CortenDesk 0.9.5
New
-
End a live session from the console (#9). Active Sessions gains a Disconnect action.
Revoking a user's access has never interrupted a session already in progress, and could not: a remote-control session runs peer-to-peer through hbbs/hbbr and never passes through the console. Forcing a logout kills the account's tokens and console sessions immediately, but a session already running is a different thing entirely.
The console now records the request and the controlled device closes the connection on its next heartbeat — up to 15 seconds on an idle device, around 3 while it is carrying a session. The row shows Disconnecting… until the device confirms the close, rather than pretending the session has already ended.
Requires device write access, is limited to devices the operator can see, and is recorded in the console audit log.
Reported by @dragonidium11.
Notes
- One migration, applied automatically by the Docker image. No configuration changes.
- Devices with no session to close see a byte-identical heartbeat response — nothing changes for the rest of your fleet.
docker pull marcpope/cortendesk:0.9.5
docker pull ghcr.io/marcpope/cortendesk:0.9.5CortenDesk 0.9.4
A visual overhaul of the console, plus two changes from #5.
New
-
The console has been redesigned. Framed cards on a darker surface, icon-chip stat tiles, a quieter table treatment, consistent badges and empty states, and a sidebar whose active section is marked with the brand accent. The dashboard now uses the full width: connections and platform mix on one row, active sessions, client versions and alerts on the next.
It is built by binding the theme's own variables to a single token set in one stylesheet, so the light/dark toggle still works and there is still no frontend build step — nothing to compile, nothing to install.
-
Address book entries identify the machine. An entry used to render as a bare nine-digit ID with an Alias column that is empty by default, so a book of sixty entries was sixty numbers. Entries now lead with the machine name, with the ID and platform beneath it, and alias stays as the optional label it always was. Entries sort by machine name, and a filter above the table matches machine, ID, alias or user.
No migration and nothing to re-sync: entries already stored the hostname and platform your clients report — they were simply never displayed.
-
The device list shows the operating system, under the hostname beside the signed-in user. Telling one Windows build from another no longer means squinting at an icon.
Both address book changes reported by @OmarRC64.
Notes
- No configuration changes and no migrations.
- Every screen was reviewed at 390px.
docker pull marcpope/cortendesk:0.9.4
docker pull ghcr.io/marcpope/cortendesk:0.9.4CortenDesk 0.9.3
Two fixes, both from user reports.
Fixed
-
The container could not start on hosts with IPv6-only DNS (#8). The entrypoint passed the nameserver from
/etc/resolv.confstraight into nginx, which requires an IPv6 resolver in square brackets, so startup aborted withnginx: [emerg] invalid port in resolver "fd12::10". Affects Railway and anything else handing containers an IPv6-onlyresolv.conf. The same code also mis-parsed an IPv6 literal inCORTENDESK_ID_SERVER, which would have produced a broken WebSocket bridge; fixed too. Verified in containers with both IPv6-only and IPv4 resolvers.If you set
NGINX_RESOLVERas a workaround, you can drop it.Reported by @GTNeill.
-
The web client now says it needs HTTPS instead of failing with
Cannot read properties of undefined (reading 'digest')(#3). Both Web Crypto and WebCodecs are restricted to secure contexts, so the in-browser client cannot run over plainhttp://. The client now reports that on load and again before connecting, rather than throwing a raw TypeError that looks like a server misconfiguration.This is a browser restriction with no JavaScript workaround — a pure-JS hash would only move the failure to the video decoder, which cannot be polyfilled. The new Web client requires HTTPS wiki page covers how to get a trusted certificate on an internal network, including how to do it for a host that only has a private IP.
Reported by @OmarRC64.
Notes
- No configuration changes, no migrations.
- The console itself works fine over plain HTTP. Only the in-browser client needs TLS.
docker pull marcpope/cortendesk:0.9.3
docker pull ghcr.io/marcpope/cortendesk:0.9.3CortenDesk 0.9.2
Two fixes, both reported by users.
Fixed
-
Single sign-on refused users whose provider does not assert email verification (#6). 0.9.1 handled providers that omit the
email_verifiedclaim, but Authentik deliberately sendsemail_verified: falsefor every user — it has no way to confirm someone owns their address — and that was still refused outright. The claim is no longer used to refuse a sign-in. Nothing to configure: upgrade and it works.A new Settings → SSO switch, "Require the provider to assert email verification", restores the strict behaviour. Off by default. Worth turning on only if you federate with a provider whose users can choose their own email address.
Accounts are matched on the provider's stable user id (
iss+sub); an email address is consulted only for the first link, after which it decides nothing. That, the email-domain allowlist and the new-user policy are what protect an account here. -
TRUSTED_PROXIESwas silently ignored whenever the config was cached (#7). The proxy list was read withenv()during bootstrap, andphp artisan config:cache— which the Docker image and most deployments run — stops loading.env, so the setting never applied and the built-in private-range default was always used. If your reverse proxy reaches CortenDesk from a public address (Cloudflare, an external load balancer), it could not be trusted: HTTPS went undetected, assets were generated withhttp://URLs, and sign-in could loop. The list now lives inconfig/trustedproxy.phpand survives caching.Reported and diagnosed by @AJV20.
Notes
- No configuration changes are needed and no migrations are added.
- Still on 0.9.0? It could not migrate on MariaDB; 0.9.1 fixed that and this release includes it.
docker pull marcpope/cortendesk:0.9.2
docker pull ghcr.io/marcpope/cortendesk:0.9.2CortenDesk 0.9.1
Two fixes for problems reported against 0.9.0. Upgrading is recommended for everyone on MariaDB.
Fixed
- Migrations failed on MariaDB (and on any MySQL left at
explicit_defaults_for_timestamp=0). Installing or upgrading to 0.9.0 died withSQLSTATE[42000]: 1067 Invalid default value for 'expires_at'and left the database half-migrated. ANOT NULL TIMESTAMPwith no default gets an implicit'0000-00-00 00:00:00'on those servers, which Laravel's strict mode rejects. Four tables were affected. - Single sign-on refused users whose provider does not send
email_verified(#6). The claim is optional in OpenID Connect and several providers never send it — Microsoft Entra ID among them. An absent claim was treated exactly like a provider reporting the address as unverified, so those users could never be matched to an account. Absent and denied are now different: an absent claim is trusted, an explicit "not verified" is still refused, and Settings → SSO gains a switch to require the claim for providers that let people choose their own address.
Notes
- If 0.9.0 already failed on you, just upgrade and run
php artisan migrate(the Docker image does it on start). The migration that failed never created anything, so there is nothing to repair by hand. - No configuration changes are needed. Everything added in 0.9.0 still defaults to off.
docker pull marcpope/cortendesk:0.9.1
docker pull ghcr.io/marcpope/cortendesk:0.9.1