using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; var builder = DistributedApplication.CreateBuilder(args); var postgres = builder.AddPostgres("postgres") .WithDataVolume(); var gerbilDb = postgres.AddDatabase("gerbilmanager"); // Stable, LAN-facing HTTP port for the API (matches the SPA's default base URL). const int ApiHttpPort = 5179; var webapi = builder.AddProject("webapi") .WithReference(gerbilDb) .WaitFor(gerbilDb) // Pin the port so the phone-facing URL doesn't change between launches. // Proxyless so the app binds the port directly — Program.cs then widens the // host to 0.0.0.0 for LAN access (the DCP proxy binds localhost only). .WithEndpoint("http", e => { e.Port = ApiHttpPort; e.IsProxied = false; }, createIfNotExists: false) .WithExternalHttpEndpoints(); // The machine's LAN IP — the SPA runs in the phone's browser, so it must reach // the API at this address, not "localhost" (which would be the phone itself). var lanIp = GetLanIpAddress(); builder.AddViteApp("frontend", "../gerbil-manager-web") .WithReference(webapi) .WaitFor(webapi) // Pin a stable port for the phone bookmark (Vite default 5173). Proxyless so // the Vite dev server binds the port directly; vite.config host:true makes it // bind 0.0.0.0 for LAN access. .WithEndpoint("http", e => { e.Port = 5173; e.IsProxied = false; }, createIfNotExists: false) .WithExternalHttpEndpoints() .WithEnvironment(context => { // VITE_API_BASE_URL is read by the Vite dev server at launch (see // src/api/client.ts). Point it at the API's LAN-reachable URL. context.EnvironmentVariables["VITE_API_BASE_URL"] = $"http://{lanIp}:{ApiHttpPort}"; }); builder.Build().Run(); // Picks the IPv4 address of the active physical LAN adapter (Wi-Fi/Ethernet), // skipping loopback and virtual adapters (WSL/Hyper-V). Recomputed each launch // so it follows DHCP changes. static string GetLanIpAddress() { var candidates = NetworkInterface.GetAllNetworkInterfaces() .Where(ni => ni.OperationalStatus == OperationalStatus.Up) .Where(ni => ni.NetworkInterfaceType is NetworkInterfaceType.Wireless80211 or NetworkInterfaceType.Ethernet) .Where(ni => !ni.Description.Contains("Hyper-V", StringComparison.OrdinalIgnoreCase) && !ni.Description.Contains("Virtual", StringComparison.OrdinalIgnoreCase) && !ni.Name.Contains("WSL", StringComparison.OrdinalIgnoreCase) && !ni.Name.Contains("vEthernet", StringComparison.OrdinalIgnoreCase)) // Prefer adapters that have a default gateway (i.e. a real network path). .OrderByDescending(ni => ni.GetIPProperties().GatewayAddresses.Count > 0); foreach (var ni in candidates) { var ip = ni.GetIPProperties().UnicastAddresses .FirstOrDefault(a => a.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(a.Address)); if (ip is not null) { return ip.Address.ToString(); } } return "localhost"; }