feat(tailscale): add Tailscale Serve support.#115
feat(tailscale): add Tailscale Serve support.#115sarinex wants to merge 2 commits intosipeed:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds Tailscale Serve integration to NanoKVM, allowing users to expose the web UI over HTTPS with browser-trusted TLS certificates via a toggle in the Tailscale settings panel. It introduces a new backend endpoint to enable/disable tailscale serve, CLI wrappers to manage serve state, and a frontend toggle with authorization consent flow and auto-redirect behavior.
Changes:
- Backend: New
POST /api/extensions/tailscale/serveendpoint withCli.Serve()andCli.ServeStatus()CLI wrappers, plusserveandserveUrlfields added to the status response. - Frontend: "Enable HTTPS" toggle in the Tailscale device panel with auth consent flow, HTTPS URL display, disable confirmation with fallback IPs, and automatic browser redirect.
- New English i18n keys for all HTTPS-related UI strings.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
server/proto/tailscale.go |
Added Serve/ServeUrl to status response; new ServeTailscaleReq/ServeTailscaleRsp types |
server/router/extensions.go |
Registered POST /api/extensions/tailscale/serve route |
server/service/extensions/tailscale/cli.go |
New ServeStatus() and Serve(enable) methods for CLI interaction |
server/service/extensions/tailscale/service.go |
New Serve() handler; GetStatus() now includes serve state |
web/src/api/extensions/tailscale.ts |
New serve(enable) API function |
web/src/i18n/locales/en.ts |
10 new English translation keys for HTTPS feature |
web/src/pages/desktop/menu/settings/tailscale/device.tsx |
HTTPS toggle UI with auth flow, URL display, and redirect logic |
web/src/pages/desktop/menu/settings/tailscale/types.ts |
Added serve and serveUrl to Status type |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Disable "Enable HTTPS" toggle when Tailscale is not running - Show error message when auth consent was not completed - Fix swallowed error in Serve() by checking cmd.Wait() exit code
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func (c *Cli) Serve(enable bool) (string, error) { | ||
| command := serveBaseCommand | ||
| if !enable { | ||
| command += " off" |
There was a problem hiding this comment.
The disable command is constructed by appending " off" to serveBaseCommand, resulting in "tailscale serve --bg --https=443 https+insecure://localhost:443 off". This passes both the proxy target URL and off as positional arguments. The idiomatic Tailscale CLI usage for disabling is tailscale serve --https=443 off (where off replaces the target URL). While this may work in practice if the CLI ignores the extra positional arg, it's fragile and may break with future Tailscale CLI updates. Consider constructing the disable command separately (e.g., "tailscale serve --https=443 off") rather than appending to the base command.
| command += " off" | |
| parts := strings.Fields(serveBaseCommand) | |
| if len(parts) == 0 { | |
| return "", errors.New("invalid tailscale serve base command") | |
| } | |
| parts[len(parts)-1] = "off" | |
| command = strings.Join(parts, " ") |
| // If it still wants authorization, the user probably didn't authorize it properly | ||
| if (rsp.data && rsp.data.authUrl) { | ||
| setAuthConsentUrl(''); | ||
| setErrMsg(t('settings.tailscale.enableHttpsCerts')); |
There was a problem hiding this comment.
The enableHttpsCerts translation key contains HTML-like interpolation tags (<1>HTTPS certificates</1>), which are processed correctly by the <Trans> component at line 251. However, here at line 138, t('settings.tailscale.enableHttpsCerts') is used directly as an error message, which will render the raw <1> tags as literal text in the error message (e.g., "Enable <1>HTTPS certificates</1> on your tailnet to continue"). Consider using a separate translation key without interpolation tags for the error message, or stripping the tags before setting the error.
| <a href={`https://${ip.addr}`} target="_blank" rel="noreferrer" className="text-blue-500 hover:underline"> | ||
| https://{ip.addr} | ||
| </a> | ||
| </div> | ||
| ))} | ||
| <div className="flex items-center space-x-2"> | ||
| <div className="size-[16px] flex items-center justify-center"> | ||
| <Tailscale /> | ||
| </div> | ||
| <a href={`https://${status.ip}`} target="_blank" rel="noreferrer" className="text-blue-500 hover:underline"> | ||
| https://{status.ip} | ||
| </a> |
There was a problem hiding this comment.
The fallback IP links in the disable-HTTPS confirmation popover all use https:// (lines 212-214 for wired/wireless IPs, and lines 221-222 for the Tailscale IP). However, since HTTPS is being disabled, these IPs won't have valid TLS certificates, so https:// links will result in browser certificate errors. This is also inconsistent with redirectToFallback() on line 60 which correctly uses http://. These links should use http:// to match the actual protocol available after disabling HTTPS serve.
Summary
Adds Tailscale Serve integration, letting users expose the NanoKVM web UI over HTTPS with automatic TLS certificates — all from a simple toggle in the existing Tailscale settings panel.
While all Tailscale traffic is already encrypted end-to-end, browsers still display connection warnings when accessing NanoKVM via plain HTTP. This feature uses Tailscale Serve to provision public-CA-signed certificates for the device's
.ts.netdomain, giving users a fully browser-trustedhttps://<device>.tailnet.ts.netURL with zero manual certificate management.Motivation
What Changed
Backend (Go)
ServeUrlfields to GetTailscaleStatusRsp; new ServeTailscaleReq/ServeTailscaleRsp typesPOST /api/extensions/tailscale/serverouteKey implementation details
cli.Serve(enable)runstailscale serve --bg --https=443 https+insecure://localhost:443and reads the process output asynchronously:https://login.tailscale.com/f/serve...).tailscale serve status(after a short settle delay) to retrieve the resulting HTTPS URL.cli.ServeStatus()parsestailscale serve statusoutput to determine if serve is active and extracts the.ts.netHTTPS URL.serve: boolandserveUrl: stringso the frontend always reflects the current serve state on load.Frontend (React/TypeScript)
serveUrlto Status typeUser flow
.ts.netURL, the browser automatically redirects to the Tailscale IP fallback.Scope & Compatibility
tailscaleCLI binary that is already managed by NanoKVM.serveUrl) are additive — older frontends will simply ignore them.Testing
All scenarios were verified on a physical NanoKVM device with Tailscale installed and logged in:
.ts.net