Skip to content

Conversation

@ibetitsmike
Copy link
Contributor

Cross-Platform Rewrite: Avalonia UI + Linux Support

This PR rewrites the Coder Desktop application to run on both Windows and Linux using Avalonia 11 for the GUI and .NET 8 for the service layer. The existing WinUI 3 App/ project is preserved unchanged.

Architecture

App.Shared/         ← Cross-platform ViewModels, Models, Services (net8.0)
App.Avalonia/       ← Avalonia 11 desktop app (net8.0)
App.Linux/          ← Linux platform services
App.Windows/        ← Windows platform services (placeholder)
Vpn.Service/        ← Cross-platform service daemon
Vpn.Linux/          ← Unix socket transport
Vpn.Windows/        ← Named pipe transport
Packaging.Linux/    ← systemd, .desktop, build-deb.sh

What changed (13 commits)

Infrastructure (Phases 0-1):

  • Retarget Vpn/ from net8.0-windowsnet8.0
  • Create IRpcServerTransport/IRpcClientTransport abstractions
  • Make Vpn.Service cross-platform: conditional AddWindowsService()/AddSystemd(), IRpcServerTransport injection, platform-conditional config (Registry vs JSON)

Linux Platform (Phases 3-5):

  • App.Linux/: LinuxSecretServiceBackend, LinuxXdgStartupManager, LinuxNotifySendNotifier, LinuxRdpConnector, LinuxSingleInstance
  • MutagenSdk: Cross-platform transport (Named Pipes on Windows, Unix Domain Sockets on Linux)
  • Packaging.Linux/: systemd unit, .desktop file, postinst/prerm scripts, build-deb.sh

Avalonia UI (Phase 2):

  • App.Shared/: 5 Models, 16 service interfaces, 5 cross-platform service impls, 12 ViewModels
  • App.Avalonia/: 11 pages, 9 windows, TrayIcon, 5 converters, 3 custom controls, platform services

Tests (Phase 6):

  • Retarget test projects to net8.0 with platform-conditional guards

Build Verification (Linux)

Category Result
Projects building 14/14 (0 errors)
Tests passing 57/57

Key design decisions

  1. Side-by-side, not in-place: Old App/ (WinUI) preserved. App.Avalonia/ is the new cross-platform default.
  2. Interface-based platform abstraction: IDispatcher, IClipboardService, ILauncherService, IWindowService, IRpcClientTransport
  3. Conditional compilation: #if WINDOWS for Authenticode, MSBuild OS conditions for packages
  4. WebView2 → text-based: Release notes via text (WebView2 is Windows-only)

Follow-up work

  • Wire full DI in App.axaml.cs (connect real RPC/credential/Mutagen services)
  • Polish Avalonia UI styling to match WinUI version
  • Test on multiple Linux DEs (GNOME, KDE, Xfce)
  • Add CI pipeline for Linux builds
  • Move Windows-specific service implementations to App.Windows/

- Retarget Vpn/Vpn.csproj from net8.0-windows to net8.0
- Create IRpcTransport.cs in Vpn/ with IRpcServerTransport and
  IRpcClientTransport interfaces
- Create Vpn.Windows/ project (net8.0-windows):
  - Move RegistryConfigurationSource.cs from Vpn/
  - Add NamedPipeServerTransport and NamedPipeClientTransport
- Create Vpn.Linux/ project (net8.0):
  - Add UnixSocketServerTransport and UnixSocketClientTransport
- Create App.Windows/ project (net8.0-windows) placeholder
- Create App.Linux/ project (net8.0) placeholder
- Add all new projects to Coder.Desktop.sln

All cross-platform projects build successfully on Linux:
  Vpn, Vpn.Linux, App.Linux, Vpn.Proto, CoderSdk, MutagenSdk
Phase 1: Retarget Vpn.Service from net8.0-windows to net8.0 and add
platform-conditional code for Windows/Linux support.

Changes:
- Vpn.Service.csproj: retarget to net8.0, add EnableWindowsTargeting,
  conditional Windows/Linux package references and project references,
  WINDOWS preprocessor constant on Windows builds
- Program.cs: platform-conditional hosting (AddWindowsService vs
  AddSystemd), config (Registry vs JSON file), transport registration
  (NamedPipeServerTransport vs UnixSocketServerTransport), log paths
- ManagerConfig.cs: add ServiceRpcSocketPath for Linux, platform-
  conditional defaults for TunnelBinaryPath and SignatureSigner
- ManagerRpc.cs: replace direct named pipe code with IRpcServerTransport
  abstraction, inject transport via constructor
- TelemetryEnricher.cs: cross-platform device ID (Windows Registry vs
  /etc/machine-id), platform-conditional DeviceOs string
- Downloader.cs: wrap AuthenticodeDownloadValidator in #if WINDOWS
- Manager.cs: platform-conditional binary download URL
  (coder-linux-{arch} vs coder-windows-{arch}.exe), guard Authenticode
  validator instantiation with #if WINDOWS
- Delete packages.lock.json (RestorePackagesWithLockFile=false)
…ging

Phase 3: App.Linux/ — Linux platform service implementations:
- LinuxSecretServiceBackend: secret-tool CLI for credential storage
- LinuxXdgStartupManager: XDG autostart desktop entry
- LinuxNotifySendNotifier: notify-send for notifications
- LinuxRdpConnector: xfreerdp/remmina fallback chain
- LinuxSingleInstance: Unix domain socket lock

Phase 4: MutagenSdk cross-platform:
- MutagenClient detects Named Pipe vs Unix Domain Socket transport
- Windows: existing NamedPipesConnectionFactory
- Linux: direct Socket + NetworkStream to UDS

Phase 5: Packaging.Linux/ — Linux packaging:
- systemd unit file (Type=notify, root for TUN)
- .desktop file with coder:// URI handler
- postinst/prerm scripts for service lifecycle
- build-deb.sh for amd64/arm64 .deb creation
- Default config.json template
Phase 6: Cross-platform test infrastructure:
- Tests.Vpn: net8.0-windows → net8.0
- Tests.Vpn.Service: net8.0-windows → net8.0 with EnableWindowsTargeting
  - AuthenticodeDownloadValidatorTest wrapped in #if WINDOWS
  - AssemblyVersionDownloadValidatorTest marked [Platform("Win")]
  - TelemetryEnricherTest updated for platform-conditional DeviceOs
- Vpn.DebugClient: net8.0-windows → net8.0
  - Platform-conditional connect (Named Pipe vs Unix Socket)
- App.csproj and Tests.App.csproj: added EnableWindowsTargeting

All cross-platform tests pass on Linux:
- Tests.Vpn: 24 passed
- Tests.Vpn.Proto: 9 passed
- Tests.CoderSdk: 8 passed
- Tests.Vpn.Service: 16 passed (Windows-only tests skipped)
Phase 2.0 (partial): Foundation for Avalonia UI migration.

App.Shared/ (net8.0) contains:
- Models: all 5 model files (CredentialModel, RpcModel, Settings,
  SyncSessionControllerStateModel, SyncSessionModel)
- Service interfaces: IRpcController, ICredentialManager, ICredentialBackend,
  IUserNotifier, INotificationHandler, IStartupManager, IRdpConnector,
  IHostnameSuffixGetter, ISyncSessionController, IUpdateController,
  ISettingsManager, IUriHandler
- New abstractions: IDispatcher (replaces DispatcherQueue),
  IClipboardService (replaces Windows Clipboard),
  ILauncherService (replaces Windows.System.Launcher),
  IWindowService (abstracts window creation for ViewModels)
- Utils: ModelUpdate, FriendlyByteConverter (static utility)

Builds with 0 errors on Linux. Next: copy cross-platform service
implementations and ViewModels.
Copy and adapt service implementations from App/:
- CredentialManager.cs (without WindowsCredentialBackend)
- RpcController.cs (refactored: IRpcClientTransport replaces NamedPipeClientStream)
- HostnameSuffixGetter.cs (as-is, cross-platform)
- SettingsManager.cs (as-is, JSON file storage)
- UriHandler.cs (as-is, business logic only)

RpcController now injects IRpcClientTransport for platform-agnostic
RPC connections (Named Pipes on Windows, Unix Sockets on Linux).

Builds with 0 errors on Linux.
Add SettingsViewModel and TrayWindowDisconnectedViewModel - the only
ViewModels with zero WinUI dependencies.

The remaining 10 ViewModels are deeply coupled to WinUI types
(DispatcherQueue, ImageSource, FrameworkElement, ContentDialog, etc.)
and will be adapted during the Avalonia UI conversion phase when the
actual UI framework types are available to replace them with.

This commit establishes the pattern: cross-platform ViewModels live in
App.Shared/, WinUI-coupled code stays in App/ until Avalonia equivalents
are ready.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant