№ A · 05 / Desktop apps · Mesh & Fleet
OpenDesk in the dock,
the same web stack in app windows.
On Mesh and Fleet managed laptops, every OpenDesk app has its own icon. Mail, Calendar, Contacts, Tasks, Files, Chat, Meet, Meshjects, Wiki, Notes. Same web stack under the hood, one Single Sign-On covering all of them. Microsoft ships Outlook and Word as separate Start-menu apps; we do the same with the OpenDesk suite. Pushed by Fleet on first agent sync, identical on Mesh and Fleet.
№ C1 / The seven launchers
Seven icons,
one Single Sign-On.
Six of the seven launchers are Chromium app-mode windows that share a dedicated Work profile, which is what makes the Keycloak cookie carry across all of them. One of those six, the OX App Suite launcher, covers Mail, Calendar, Contacts and Tasks as internal tabs in one window (Outlook-style). The seventh, Chat, is Element Desktop (real native app). Files also installs Nextcloud Sync alongside its Chromium wrapper for filesystem integration.
Launcher
Wraps
URL
Implementation
Mail · Calendar · Contacts · Tasks
OX App Suite
mail.od.<cust>.kiwistack.io
Chromium app-mode · one window, internal tabs
Files
Nextcloud
files.od.<cust>.kiwistack.io
Chromium app-mode + Nextcloud Sync
Chat
Element + Synapse
talks to homeserver directly
Element Desktop · upstream native
Meet
Jitsi
meet.od.<cust>.kiwistack.io
Chromium app-mode
Meshjects
OpenProject
projects.od.<cust>.kiwistack.io
Chromium app-mode
Wiki
XWiki
wiki.od.<cust>.kiwistack.io
Chromium app-mode
Notes
CryptPad
notes.od.<cust>.kiwistack.io
Chromium app-mode
URL patterns above follow the *.od.<customer>.kiwistack.io shape. If a customer uses path-based routing instead (od.<customer>.kiwistack.io/mail), so the wrapping mechanism is identical. Mesh and Fleet get the same set on Linux / macOS / Windows; Core is not in scope (no managed devices in that tier).
№ C2 / How it's wrapped
Three OSes,
one shared profile.
The mechanics are intentionally boring: a shortcut that invokes Chromium with --app= and a profile flag. No Electron build, no Tauri binary, no per-customer signing. The hard part (sandboxing, mTLS, FIDO2, WebRTC, codecs, autoupdate) is Chromium's job, and it's the same browser engine the customer would have hit in a tab anyway.
OS
Install path
Invoke
Icons
Policy file
Linux
Fleet default · the seamless track
.desktop file in /usr/share/applications/
chromium --app=<URL> --profile-directory=Work
PNG set under /usr/share/icons/hicolor/
/etc/chromium/policies/managed/kiwistack.json
macOS 14+
BYOL · browser-based first auth
Minimal .app bundle in /Applications/KiwiStack/
Shell wrapper exec'ing Chrome with --app=<URL>
Embedded in .app bundle
/Library/Managed Preferences/org.chromium.Chromium.plist
Windows 11
BYOL · browser-based first auth
.lnk shortcuts in Start Menu and on Desktop
chrome.exe --app=<URL> --profile-directory=Work
.ico embedded in shortcut
HKLM\SOFTWARE\Policies\Chromium\
The "Work" Chromium profile is a single profile shared across every launcher. One Keycloak cookie sits in it, and every app window reads from that one cookie. Personal browsing stays in the user's default profile, untouched. Fleet drops the policy file at first agent sync; the launcher shortcuts are dropped in the same step.
№ C3 / SSO without re-prompts
Two tracks,
one Keycloak.
Linux + SSSD is the seamless track: the Kerberos TGT acquired at OS login authenticates every app via SPNEGO Negotiate against Keycloak, no prompt ever. macOS and Windows BYOL don't have that OS-level Kerberos plumbing, so the first launch shows the Keycloak login once, then every other launcher reuses the cookie until session expiry. Same flow as A6 documents for Firefox, just in app-mode windows.
Fleet default
№ 01
Linux + SSSD
Kerberos auto-SSO from the OS session into every app launcher. Zero re-prompts as long as the TGT is alive.
- 01 User logs in at the GDM screen with their OpenDesk credential. SSSD validates against Nubus over the WireGuard tunnel and gets a Kerberos TGT, cached in /tmp/krb5cc_<uid>.
- 02 User clicks the Mail icon. Chromium app-mode window opens at mail.od.<customer>.kiwistack.io.
- 03 Chromium policy AuthServerAllowlist=*.od.<customer>.kiwistack.io is in force. Chromium reads the TGT from the kernel keyring, sends a SPNEGO Negotiate header.
- 04 Keycloak (in Nubus) validates the Kerberos ticket against its KDC, issues an OIDC token, drops a session cookie in the Work profile.
- 05 App loads, signed-in. Click Calendar, Files, Meet: same Work profile, same cookie, no prompts. TGT auto-renews every 10h via SSSD.
Mesh · cookie-persisted
№ 02
macOS / Windows BYOL
First launch redirects to Keycloak in the app window. After the password + YubiKey FIDO2 dance, the cookie is set in the Work profile and every other app reuses it.
- 01 User clicks the Mail icon for the first time after enrollment. Chromium app-mode window opens at mail.od.<customer>.kiwistack.io.
- 02 OX redirects to Keycloak. User types their password and touches the YubiKey for FIDO2.
- 03 Keycloak issues an OIDC token, drops a session cookie in the Work profile.
- 04 User opens Calendar, Files, Meet: same Work profile, same cookie, signed in everywhere without re-prompting.
- 05 Cookie lives until Keycloak's session expiry (default ~10h sliding). After that, one app re-prompts; the rest follow.
Element Desktop and Nextcloud Sync don't go through this flow. Element Desktop authenticates via OIDC against Keycloak as a Matrix client (configured by Fleet), and Nextcloud Sync uses an app password issued by the Files web UI on first link. Apple Platform SSO (macOS 13+) and Windows WAM could close the first-launch prompt on those OSes; both are tracked as future enhancements.
№ C4 / Two native exceptions
Real apps,
where they earn it.
When a real native binary already exists upstream and beats the Chromium wrapper on UX, we use the binary. That's two cases: Element Desktop replaces the Chat wrapper; Nextcloud Sync runs alongside the Files wrapper for the filesystem layer. Everything else is a wrapper.
Replaces the Chromium wrapper for Chat
№ 01
Element Desktop
Real native app from upstream. System tray, persistent presence, native notifications, message-on-demand sync, all things wrapping the web frontend would lose.
Install
Fleet installs the upstream .deb / .dmg / .msi. Pre-configures the homeserver URL to chat.od.<customer>.kiwistack.io. First launch authenticates via OIDC against Keycloak (the Matrix server is a Keycloak SP).
Why native
Wrapping Element web in Chromium app-mode would be a downgrade. No system-tray icon, no native notifications, no presence when the window is closed. The upstream Electron build solves all of that already.
Alongside the Files Chromium wrapper
№ 02
Nextcloud Sync
True filesystem integration. The Chromium wrapper stays for browse, share, the web UI. Sync handles the bytes-on-disk layer next to it.
Install
Fleet installs the upstream client. Pre-configures the server URL files.od.<customer>.kiwistack.io. First launch links via app password issued by the Files web UI.
Why native
The web UI is the right place to manage shares, see versions, exercise spreadsheet preview. The local filesystem is the right place to drop files into a folder and have them sync. Both belong on the laptop; they're complementary, not redundant.
№ C5 / Cross-app links
A link between apps,
the browser opens, for now.
A *.od.<cust> link clicked from inside one app-mode window opens in the default browser, not in the matching dock-icon launcher. Keycloak session is shared, so the cost is "tab instead of app window", not "log in again". A v2 dispatcher closes the gap.
01
Today, default browser
Chromium app-mode hands cross-origin clicks to the OS, which routes to the default browser. Our app-mode launchers are shortcuts, not registered URL handlers; the OS doesn't know they exist.
02
v2, URL dispatcher
Set the default browser to a tiny wrapper: *.od.<cust>.kiwistack.io → exec the matching chromium --app=... launcher; else → exec the real default browser. Linux via xdg-mime + a wrapper .desktop; macOS and Windows have equivalents.