feat(desktop-server): detect the server's IP and validate the server host
Summary
Gauzy Server and Gauzy API Server get a better "Server Hostname / IP Address" field, in the setup screen and in the settings:
- Detect my IP fills in this computer's best network address, and the computer's addresses are listed under the field to pick one. Addresses come from the operating system only (
os.networkInterfaces()); there is no third-party lookup. - Invalid hosts are blocked. A value that is neither an IP address nor a host name (for example
999.1.1.1orbad host!) shows an inline error, and Save is disabled until it is fixed. - SSL with an IP address gets a hint, not a block. With "Use SSL" on and an IP address (including
0.0.0.0) as the host, the field suggests a host name. Certificates that list an IP address still work.
Every value accepted today is still accepted: an empty value (the server then uses its default, 0.0.0.0), 0.0.0.0, 127.0.0.1, LAN addresses, IPv6, localhost, and host names, each with or without http(s):// and a port.
Why
- #7547: the server's external IP address should be detected automatically and offered as an option.
- #7548: a "Detect my IP" button next to the IP field.
- #7549: when "Use SSL" is on, the host should be a host name rather than an IP address. Done as a warning: a hard block would reject valid setups such as IP SAN certificates or self-signed internal certificates.
- #7550: show an error when saving an invalid IP address. Before this PR, any text was saved and later broke the API URL the server builds from it (
ServerConfig.uiHostName).
What changed
@gauzy/contracts
IDesktopNetworkAddress(name, address, family, virtual) and the IPC channelDESKTOP_NETWORK_ADDRESSES_CHANNEL(GET_NETWORK_ADDRESSES), shared by the main process and the renderer.
@gauzy/desktop-lib (new lib/network/)
listDesktopNetworkAddresses(): a pure helper that lists the addresses best first:- loopback and link-local addresses (
169.254.x.x,fe80::) are left out; - the computer's own adapters come before virtual, container and VPN adapters (Docker, WSL/Hyper-V, VirtualBox, VMware, Tailscale, WireGuard and similar), whose name patterns are an exported, overridable list;
- IPv4 comes before IPv6, and private LAN ranges come first;
- Node 18.0 to 18.3 reported the address family as a number; that form is handled too.
- loopback and link-local addresses (
registerDesktopNetworkAddressesHandler(): registersipcMain.handlefor the channel and can safely be called again.
apps/server, apps/server-api
- One call each to register the handler, next to the existing
app_settinghandler.
@gauzy/desktop-ui-lib (new settings/server-host/)
server-host.util.ts: pure helpers.parseServerHostreads the scheme, the host (IPv4, IPv6 with or without brackets, a name with underscores, or an internationalised name), the port and the path.- An IPv4 octet with a leading zero only counts where browsers read it the same (
192.168.001.001):192.168.1.020(opened as.16) and192.168.1.099(refused) are blocked. A non-ASCII name is checked the URL way, punycode throughnew URLand then the ASCII label rules, sobücher.examplepasses andbad/host(fullwidth slash) does not. A name ending in a decimal or0xhexadecimal label (192.168.1,gauzy.0xff) is blocked too: browsers read it as an IPv4 address. An IPv4 address may end with a dot (192.168.1.20.), as any URL host can. checkServerHostreturns the error, or the warnings from an extensible list of rules,SERVER_HOST_WARNING_RULES.- Also
isValidServerHost,normalizeServerHost(trims, and writeshttp(s)://in lower case, as the server only recognises it so),reachableServerAddresses(the address families other computers can reach the servers on: IPv4),toServerHostAddressandwithDetectedAddress.
NetworkAddressService: wraps the IPC call. It drops malformed entries and returns an empty list when the app has no handler.<gauzy-server-host-input>: a standalone OnPush component built on signals ([(host)],[disabled],[sslEnabled]). It shows the error and warning captions, lists the detected IPv4 addresses, and has the Detect my IP button. With only IPv6 addresses, Detect keeps the value and says why, and a Detect result that arrives after the field was locked is ignored. The input has an accessible label. Nothing is picked for the user: the value only changes when they type, choose an address or press Detect.- Setup screen (
isServeronly):- the field uses the component and takes a full row;
validation()keeps Save and Check disabled for an invalid host;saveAndRun()checks the host again before starting the server, as it can change while the database check runs, and the field is locked while the check, the save and the server start run;- the SSL hint follows the SSL form, also after Browse replaces its config;
- the saved host is trimmed.
- Settings screen (
isServeronly):- the field uses the component;
- Save is disabled for an invalid host, and
restartApp()also refuses an invalid host with a toast; - settings saved straight away (SSL, auto start) store the host trimmed, without changing the text being edited, and leave an invalid host out, so the stored config keeps the last saved one;
- the saved host is trimmed.
- Non-blocking hints also cover values the server turns into broken URLs today:
- an IPv6 address (with LAN access on, the servers listen on
0.0.0.0, which accepts IPv4 connections only); - a port typed into the host (the ports have their own fields);
- a path after the host.
- an IPv6 address (with LAN access on, the servers listen on
@gauzy/ui-core i18n
- New
TIMER_TRACKER.SETTINGS.SERVER_HOST.*keys in all 13 languages that have the desktop settings strings: en, ar, bg, de, es, fr, he, it, nl, pl, pt, ru, zh.ach.jsonhas noTIMER_TRACKER.SETTINGSblock, so it is left as it is.
Flags / settings and their defaults
- No new setting or flag. The host default (
0.0.0.0) is unchanged, and nothing is pre-selected or changed for the user. - The only new block is on values that are neither an IP address nor a host name. These values never produced a working server URL.
Tests
| Command | Result |
|---|---|
NODE_OPTIONS=--experimental-vm-modules npx jest -c packages/desktop-lib/jest.config.ts --rootDir packages/desktop-lib | 15 suites, 209 tests passed (new: network/desktop-network-addresses.spec.ts, network/desktop-network-ipc.spec.ts, 12 tests) |
npx jest -c packages/desktop-ui-lib/jest.config.ts --rootDir packages/desktop-ui-lib | 52 suites, 294 tests passed. New: server-host.util.spec.ts, network-address.service.spec.ts, server-host-input.component.spec.ts, setup/setup-server-host.spec.ts and settings/settings-server-host.spec.ts |
npx tsc -p packages/desktop-lib/tsconfig.lib.json --noEmit, npx tsc -p packages/contracts/tsconfig.lib.json --noEmit | clean |
npx tsc -p packages/desktop-ui-lib/tsconfig.lib.json --noEmit | no new errors. The only errors are the 28 google namespace errors in ui-core/shared that are already on develop |
ngc (AOT, full template type-check) on setup.component.ts + settings.component.ts | clean. A deliberate template error was caught, as a control |
eslint on the new files | no findings. The edited files have the same error count as develop |
The desktop-ui-lib suites need the generated packages/ui-config/src/lib/environments/environment.ts (yarn config:dev, as test-unit.yml does).
The specs cover the cases from the plan:
- Save is disabled for
999.1.1.1andbad host!and stays enabled for192.168.1.20andgauzy.example.com; - every host form accepted today is still accepted;
- the SSL hint appears with an IP address and not with a name, and follows the SSL toggle;
- Detect fills in the best address and keeps
http(s)://; - only IPv4 addresses are offered and filled in; with only IPv6 addresses, Detect keeps the value and says why;
- leading-zero IPv4 octets, fullwidth separators and internationalised names (
bücher.examplestays accepted); - setup checks the host again before starting the server, and settings stored at once store the host trimmed;
- when no address is found, a toast is shown and the value is kept;
- the field is locked while the server runs.
Manual verification still needed
I could not run the Electron apps here. Please check in a Gauzy Server dev build:
- Setup, step 4: the addresses of this computer are listed under the host field, with the LAN address first. Detect my IP fills it in. With LAN access on, another computer on the network opens the UI at that address.
- Typing
999.1.1.1shows the error and disables Check and Save. - Settings, General tab: the same field. With "Use SSL" on, an IP address shows the hint. An invalid host disables Save.
- Gauzy API Server: the same field (it has no UI port).
Risk and rollback
- Low. The renderer changes only touch the host field of the server apps (
isServer). Gauzy Desktop and the Desktop Timer do not render the field and are not affected. - The main process gains one read-only IPC handler that returns local interface addresses to the app's own windows. There is no network call.
- To roll back, revert this PR. Nothing is migrated or stored differently.
Open questions
- The listing puts virtual, container and VPN adapters last instead of hiding them, because a Tailscale or VPN address can be the one a remote timer uses. Should they be hidden instead?
- The host field still accepts a port, a path and an IPv6 address (with a hint each) because they were accepted before, even though the servers cannot be reached through them:
ServerConfig.uiHostNameturns a port or a path into a broken URL, and the listeners are IPv4 only. A follow-up could normalise them inserver-config.ts, which another lane is changing at the moment, or make the listeners dual-stack.
Closes #7547
Closes #7548
Closes #7549
Closes #7550
🤖 Generated with Claude Code
Summary by cubic
Improves the "Server Hostname / IP Address" field in the setup and settings screens of Gauzy Server and Gauzy API Server: it now offers this computer's network addresses, has a "Detect my IP" button, and blocks values that are neither an IP address nor a host name.
Previously any text was saved and later broke the API URL the server builds from it. Now values like 999.1.1.1 or bad host! show an inline error and keep Save disabled. With SSL on, an IP address gets a non-blocking hint to use a host name, so valid setups like IP SAN certificates still work.
What changed
- A new
GET_NETWORK_ADDRESSESIPC handler in both server apps lists addresses fromos.networkInterfaces()only, with no third-party lookup. Loopback and link-local addresses are left out, and virtual, container, and VPN adapters come last. - A new
gauzy-server-host-inputcomponent replaces the plain host input in both screens, with error and warning captions, a list of detected addresses, and the Detect button. - Only IPv4 addresses are offered and filled: with LAN access on, the servers listen on
0.0.0.0, which accepts IPv4 only. If only IPv6 addresses exist, the host is kept and the user is told why. A typed IPv6 host stays accepted, now with a warning. - IPv4 octets with a leading zero that browsers read as another number or refuse are blocked; ones that mean the same in both readings stay accepted, as does an IPv4 host with a trailing dot. A name ending in a hex (
0x) label is rejected like one ending in a decimal number. Internationalized names are checked the URL way (punycode then ASCII label rules), with a non-numeric last label added first so the URL check does not rewrite a host that looks like an IPv4 address. - Setup re-checks the host before starting the server and locks the field while the check, save, and server start run; late Detect results are ignored once the field is locked, and the SSL hint stays current when the SSL form replaces its config object. Settings stored at once (SSL, auto start) store the host trimmed and with the
http(s)://scheme in lower case (the server would otherwise prependhttp://to it), as Save does, without changing the text being edited. - Hints also cover a bare IPv6 address, a port, and a path in the host, which the server turns into broken URLs.
- Every value accepted today is still accepted: empty,
0.0.0.0,127.0.0.1, LAN and IPv6 addresses,localhost, and host names, with or withouthttp(s)://and a port. - New i18n keys in the 13 languages that have the desktop settings strings.