Skip to content

feat: migrate desktop app from Electron to Wails v3#6

Open
gustavosbarreto wants to merge 3 commits into
masterfrom
feature/migrate-to-wails
Open

feat: migrate desktop app from Electron to Wails v3#6
gustavosbarreto wants to merge 3 commits into
masterfrom
feature/migrate-to-wails

Conversation

@gustavosbarreto
Copy link
Copy Markdown
Member

What

Migrates ShellHub Desktop from Electron to Wails v3 (Go backend + Vue 3/Vuetify frontend),
producing a single self-contained binary that uses the OS-native webview instead of bundling Chromium
(~18 MB vs ~150 MB).

Why

Lighter runtime, native webview, and a Go backend that unifies the stack with the rest of ShellHub
and unlocks native capabilities (local SSH keys, and later an in-process agent).

How it works

Wails has no nested <webview>, so the active instance's web UI is embedded in an <iframe> fed by a
small Go reverse proxy:

  • internal/proxy — dynamic-target reverse proxy on localhost that strips X-Frame-Options/CSP and
    proxies the SSH-terminal websocket (which the Wails asset server can't do on Linux).
  • internal/services:
    • InstanceService — server-side /info validation (no CORS) + instance switching.
    • SSHService — ssh-agent-style ListPublicKeys/Sign; private keys never leave Go, signing
      gated by a native consent dialog.
  • frontend/src/services/bridge.tspostMessage relay so the embedded (cross-origin) web UI can
    invoke whitelisted native capabilities. Protocol in docs/native-bridge.md.

Frontend: moved src/rendererfrontend, <webview> → iframe (proxy-fed), window controls/drag
via @wailsio/runtime, /info validation via Go bindings. Removed Electron main/preload,
electron-vite/electron-builder configs, dead template assets, and unused Login/router code.

Packaging / CI

  • Build scheme is the Wails Taskfiles (wails3 task package) on native runners.
  • Linux: AppImage + .deb + .rpm; Windows: NSIS; macOS: .app.
  • Stack is GTK4 / WebKitGTK 6.0 (matches nfpm.yaml).
  • AppImage: the linuxdeploy GTK plugin still references the obsolete libcroco; the CI installs it on
    the build host so it gets bundled into the AppImage (stays self-contained). See
    docs/packaging.md.

Test plan

  • wails3 buildbin/shellhub; app launches and loads cloud.shellhub.io in the iframe
  • frontend typecheck, go vet, go build all green
  • .deb builds via nfpm with correct branding/deps
  • AppImage bundling verified (binary + WebKit libs; libcroco resolved by providing it on the host)
  • CI run needed to confirm Windows (NSIS) and macOS (.app) jobs — couldn't run GitHub
    Actions locally

Follow-ups (out of scope)

Replace the Electron shell with a Wails v3 (Go backend + Vue 3/Vuetify
frontend) app, producing a single self-contained binary that uses the OS
native webview instead of bundling Chromium.

Core architecture (Wails has no nested <webview>):
- internal/proxy: dynamic-target reverse proxy on localhost that strips
  X-Frame-Options/CSP and proxies the SSH-terminal websocket, so the active
  instance's web UI can be embedded in an <iframe>.
- internal/services: InstanceService (server-side /info validation, instance
  switching) and SSHService (ssh-agent-style ListPublicKeys/Sign; private keys
  never leave Go, signing gated by a native consent dialog).
- frontend/src/services/bridge.ts: postMessage relay so the embedded (cross
  -origin) web UI can invoke whitelisted native capabilities. Protocol in
  docs/native-bridge.md.

Frontend: moved src/renderer -> frontend, replaced <webview> with an iframe
fed by the proxy, window controls/drag via @wailsio/runtime, /info validation
via Go bindings. Removed Electron main/preload, electron-vite/builder configs,
dead template assets and unused Login/router code.

Packaging: Wails build scaffolding with ShellHub branding (io.shellhub.app);
CI reworked to build AppImage/.deb/.rpm (Linux), NSIS (Windows), .app (macOS).
See docs/packaging.md for the GTK4/GTK3 + AppImage libcroco caveats.
The SSHService (ListPublicKeys/Sign + consent dialog) and the wired
postMessage relay were only a proof-of-concept that the native bridge is
feasible. The real ShellHub web UI does not call anything in the desktop
shell yet, so:

- delete internal/services/ssh.go and its SSHService registration in main.go
- unwire the bridge from AppLayout (no installNativeBridge call)
- keep frontend/src/services/bridge.ts as a documented example/template
  (not installed) for when native capabilities are needed later
- trim docs/native-bridge.md to reflect the dormant state; SSH signing stays
  as a worked example of a sensitive, consent-gated capability
- regenerate bindings (InstanceService only)

InstanceService (server-side /info validation + proxy target switching) is
unchanged and remains the only Go service the chrome calls.
- Per-instance reverse proxies (proxy.Manager): each instance gets its own
  loopback port/origin, so cookies/storage are isolated per instance and a
  proxy's target never changes (in-flight requests can't be routed to the
  wrong instance after a switch). Replaces the single mutable-target proxy.
- Validate instance URLs in Ensure (require http/https + non-empty host)
  instead of letting a schemeless URL fail with an opaque 502.
- Strip the Domain attribute from Set-Cookie so instance session cookies
  bind to the loopback origin and persist (were dropped on domain mismatch).
- Inject a script into proxied HTML that routes target=_blank / window.open
  to a *different* origin through the chrome, which opens them in the system
  browser (Browser.OpenURL, http/https only) — Wails has no new-window hook,
  so external links otherwise failed or navigated the iframe away.
- Frontend: tear the old iframe document down (about:blank) before swapping
  instance; back the loading spinner with a 20s timeout + @error so it can't
  stick on; validate instances in parallel instead of serially; handle
  SetActiveInstance errors; guard removeInstance index.
- Remove dead ProxyURL binding; add proxy unit tests.
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