diff --git a/AGENTS.md b/AGENTS.md index 6b69b60..9414a9f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,13 +54,14 @@ dotnet run --project src/HSchool.AppHost ``` `run-aspire.cmd` is the Windows entry point: it skips MSBuild when AppHost and Server dlls are -newer than C# / csproj / props. Pass `--rebuild` to force a build. Keep the script in sync with -`tools/apphost-uptodate.ps1` if the AppHost path ever moves. +newer than C# / csproj / props. Pass `--rebuild` to force a build. When `tailscale` is on PATH +it also runs `tailscale serve --bg 5173` when `tailscale` is on PATH and connected; otherwise +it warns and continues locally only. Pass `--no-tailscale` to skip Serve. Keep the script in sync +with `tools/apphost-uptodate.ps1` if the AppHost path ever moves. `dotnet run --project src/HSchool.AppHost` still compiles every time — that is for a dirty tree, not a fast relaunch. It starts the server *and* the Vite dev server and opens the Aspire -dashboard. The Vite port is assigned per run (`npm run dev -- --port `), so read the -client URL off the dashboard instead of assuming 5173. +dashboard. The Vite client listens on port **5173** (pinned in AppHost for Tailscale Serve). Do not start a dev server with a bare `npm run dev` when you meant to run the whole app — the client only finds the backend through the Aspire-injected `SERVER_HTTP` environment variable, or diff --git a/README.md b/README.md index 3c6015f..c8be9af 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,27 @@ and Node are on PATH first and passes any arguments through `run-aspire.cmd --rebuild` forces a build. The Aspire dashboard opens with two resources: `server` (ASP.NET Core) and `client` (Vite dev -server). Aspire assigns the client a random port on every run, so take its URL from the dashboard -rather than guessing. +server on port **5173**). + +## Share with friends on Tailscale + +For a dev session over your tailnet, expose only the Vite client — not the game server (5180), +Aspire dashboard (15180), or SwarmUI. + +`run-aspire.cmd` runs `tailscale serve --bg 5173` before the AppHost when `tailscale` is on +PATH and connected. If Tailscale is missing, offline, or `serve` fails, the script prints a +warning and continues with local access only (`http://localhost:5173`). Pass `--no-tailscale` +to skip Serve entirely. You can also run the command yourself once per boot if you start with +`dotnet run --project src/HSchool.AppHost` instead. + +Friends on the same tailnet open `https://..ts.net`. The page, REST API +and game WebSocket all go through Vite’s proxy on one origin; Kestrel stays on localhost. + +Change `HSchool:AlphaPassword` from the repo default before sharing the link. Optional: set +`HSCHOOL_TAILSCALE_SERVE=1` in the client process environment if you want Vite HMR through Serve +(`wss` on port 443); gameplay works without it. + +Do **not** add port 5180 or the Aspire dashboard to `tailscale serve`. ## What you can do diff --git a/run-aspire.cmd b/run-aspire.cmd index cb97a5b..0bcf1ea 100644 --- a/run-aspire.cmd +++ b/run-aspire.cmd @@ -5,6 +5,7 @@ rem Starts the whole app: game server, Vite client and the Aspire dashboard. rem Skips MSBuild when AppHost and Server dlls are newer than C# / csproj / props. rem run-aspire.cmd --launch-profile http rem run-aspire.cmd --rebuild +rem run-aspire.cmd --no-tailscale skip `tailscale serve --bg 5173` cd /d "%~dp0" @@ -25,6 +26,7 @@ if errorlevel 1 ( set "CONFIG=Debug" set "REBUILD=0" +set "TAILSCALE_SERVE=1" set "ARGS=" :parse @@ -34,6 +36,11 @@ if /I "%~1"=="--rebuild" ( shift goto parse ) +if /I "%~1"=="--no-tailscale" ( + set "TAILSCALE_SERVE=0" + shift + goto parse +) if /I "%~1"=="-c" if not "%~2"=="" set "CONFIG=%~2" if /I "%~1"=="--configuration" if not "%~2"=="" set "CONFIG=%~2" set "ARGS=!ARGS! %1" @@ -74,6 +81,31 @@ if errorlevel 1 ( echo. :run +if "%TAILSCALE_SERVE%"=="1" ( + where tailscale >nul 2>&1 + if errorlevel 1 ( + echo [run-aspire] WARNING: tailscale not found in PATH. + echo Friends cannot reach the client over the tailnet. + echo Continuing with local access only ^(http://localhost:5173^). + ) else ( + tailscale status >nul 2>&1 + if errorlevel 1 ( + echo [run-aspire] WARNING: tailscale is not connected or not running. + echo Friends cannot reach the client over the tailnet. + echo Continuing with local access only ^(http://localhost:5173^). + ) else ( + echo [run-aspire] tailscale serve --bg 5173 + tailscale serve --bg 5173 + if errorlevel 1 ( + echo [run-aspire] WARNING: tailscale serve failed. + echo Friends cannot reach the client over the tailnet. + echo Continuing with local access only ^(http://localhost:5173^). + ) + ) + ) + echo. +) + echo [run-aspire] Starting the Aspire AppHost. Press Ctrl+C to shut everything down. echo. diff --git a/src/HSchool.AppHost/AppHost.cs b/src/HSchool.AppHost/AppHost.cs index 5464a3c..1d3d424 100644 --- a/src/HSchool.AppHost/AppHost.cs +++ b/src/HSchool.AppHost/AppHost.cs @@ -23,7 +23,9 @@ if (headless) if (!headless) { + // Pin the Vite port so `tailscale serve --bg 5173` always targets the client. var client = builder.AddViteApp("client", "../HSchool.Client") + .WithHttpEndpoint(port: 5173, targetPort: 5173) .WithReference(server) .WaitFor(server); diff --git a/src/HSchool.Client/vite.config.ts b/src/HSchool.Client/vite.config.ts index 1a57a55..49920f7 100644 --- a/src/HSchool.Client/vite.config.ts +++ b/src/HSchool.Client/vite.config.ts @@ -4,8 +4,16 @@ import { defineConfig } from 'vitest/config'; // so the dev server proxies to whatever port the backend actually got. const backend = process.env.SERVER_HTTPS ?? process.env.SERVER_HTTP ?? 'http://localhost:5180'; +// Set HSCHOOL_TAILSCALE_SERVE=1 when exposing Vite through `tailscale serve` so HMR +// uses wss:443 instead of the dev-server port. Gameplay does not need this. +const tailscaleServe = process.env.HSCHOOL_TAILSCALE_SERVE === '1'; + export default defineConfig({ server: { + port: 5173, + strictPort: true, + allowedHosts: ['.ts.net'], + hmr: tailscaleServe ? { protocol: 'wss', clientPort: 443 } : undefined, proxy: { '/api': { target: backend,