[Dev][Game] Dungeon crawler unreachable on the Mac local stack — game-server absent, and it serves plain ws even when started #909

Closed
opened 2026-08-04 13:51:06 +00:00 by spikerj · 1 comment
Owner

Symptom

Loading the dungeon crawler against a local Mac stack:

✅ GameSignalRService: Connected to GameHub
WebSocket connection to 'wss://localhost:7777/ws?token=...' failed
Uncaught (in promise) Event {isTrusted: true, type: 'error', target: WebSocket, ...}

The GameHub line is a red herring — that's SignalR on the API (:33333/hubs/dungeon-crawler) and it works. The game server is a separate service on :7777.

Three causes, all needed

1. game-server isn't in the Mac stack at all. COMPOSE_SERVICES in scripts/mac-dev-common.sh never included it, so nothing listens on 7777.

2. Redis + depends_on are cluster-shaped. game-server and game-cache-initializer hardcode redis-node-1:6379,redis-node-2:6379,redis-node-3:6379, but the Mac stack runs a single Redis Stack node — 2 and 3 don't resolve. game-cache-initializer also depends_on: cluster-initiator, which requires all six nodes (the same trap redisinsight already dodges with !override).

3. Even started, it serves plain ws — this is the subtle one.

The Angular dev page is HTTPS and game-server.service.ts derives its URL from window.location.protocol (environment.development.ts defines no gameServerUrl), so the browser asks for wss://. GameServerOptions.UseHttps already defaults true — but the compose service runs ASPNETCORE_ENVIRONMENT=Production, and appsettings.Production.json carries no Ssl* keys. So ConfigureHttpsListenOptions falls through both cert branches to bare listenOptions.UseHttps(), finds no ASP.NET dev cert inside the container, and hits:

catch (InvalidOperationException)
{
    Log.Warning("No certificate available - falling back to HTTP (insecure)");
}

The container then serves plain ws and reports itself perfectly healthy. Only the browser notices, as a bare failed with no detail. appsettings.Development.json does set UseHttps + SslCertificatePath: ssl/gameserver.pfx, but the container never runs under Development.

Fix

  • --game flag on mac-dev-up.sh (mirrors --full) adding game-cache-initializer, game-server, game-events-handler. Not default — 3 image builds on first use. game-events-handler is included so game events don't pile up unconsumed on RabbitMQ.
  • docker-compose.mac.yml: single-node Redis, depends_on: !override to drop the cluster chain, and bind-mount the same dev PEM the native API uses (CN=localhost, already trusted in the login keychain) with GameServer__SslCertPath/SslKeyPath.
  • mac-dev-up.sh now exports the dev cert in step 1b, before compose up — the container reads it at startup, so it must exist first.
  • Bring-up gates on the TLS handshake, not just the port: a certless server still accepts TCP, which is exactly how this stays invisible.

Verified

  • Using PEM certificate from /app/certs/backend-cert.pem / Kestrel configured for HTTPS/WSS on port 7777 (correct branch taken, not the fallback).
  • Cert presented on :7777 has SHA1 46:08:4F:CB:…:FC:A8 — byte-identical to the trusted dev cert, so the browser accepts it.
  • curl --http1.1 WebSocket upgrade to https://localhost:7777/ws → 101 Switching Protocols.
  • game-cache-initializer exits 0; Redis caches initialize; game-events-handler clean.

Use --http1.1 when testing by hand — curl negotiates HTTP/2 over ALPN by default and the upgrade returns a misleading 400.

Follow-up worth considering

environment.development.ts has no gameServerUrl while environment.ts has ws://localhost:7777 and prod has wss://gameserver.spikersoft.com. The runtime derivation happens to produce the right value, but an explicit dev entry would remove the dependency on page protocol.

## Symptom Loading the dungeon crawler against a local Mac stack: ```text ✅ GameSignalRService: Connected to GameHub WebSocket connection to 'wss://localhost:7777/ws?token=...' failed Uncaught (in promise) Event {isTrusted: true, type: 'error', target: WebSocket, ...} ``` The GameHub line is a red herring — that's SignalR on the API (`:33333/hubs/dungeon-crawler`) and it works. The game server is a **separate service** on :7777. ## Three causes, all needed **1. `game-server` isn't in the Mac stack at all.** `COMPOSE_SERVICES` in `scripts/mac-dev-common.sh` never included it, so nothing listens on 7777. **2. Redis + `depends_on` are cluster-shaped.** `game-server` and `game-cache-initializer` hardcode `redis-node-1:6379,redis-node-2:6379,redis-node-3:6379`, but the Mac stack runs a single Redis Stack node — 2 and 3 don't resolve. `game-cache-initializer` also `depends_on: cluster-initiator`, which requires all six nodes (the same trap `redisinsight` already dodges with `!override`). **3. Even started, it serves plain `ws` — this is the subtle one.** The Angular dev page is HTTPS and `game-server.service.ts` derives its URL from `window.location.protocol` (`environment.development.ts` defines no `gameServerUrl`), so the browser asks for `wss://`. `GameServerOptions.UseHttps` already defaults `true` — but the compose service runs `ASPNETCORE_ENVIRONMENT=Production`, and `appsettings.Production.json` carries no `Ssl*` keys. So `ConfigureHttpsListenOptions` falls through both cert branches to bare `listenOptions.UseHttps()`, finds no ASP.NET dev cert **inside the container**, and hits: ```csharp catch (InvalidOperationException) { Log.Warning("No certificate available - falling back to HTTP (insecure)"); } ``` The container then serves plain `ws` and reports itself perfectly healthy. Only the browser notices, as a bare `failed` with no detail. `appsettings.Development.json` *does* set `UseHttps` + `SslCertificatePath: ssl/gameserver.pfx`, but the container never runs under Development. ## Fix - `--game` flag on `mac-dev-up.sh` (mirrors `--full`) adding `game-cache-initializer`, `game-server`, `game-events-handler`. Not default — 3 image builds on first use. `game-events-handler` is included so game events don't pile up unconsumed on RabbitMQ. - `docker-compose.mac.yml`: single-node Redis, `depends_on: !override` to drop the cluster chain, and bind-mount the **same dev PEM the native API uses** (`CN=localhost`, already trusted in the login keychain) with `GameServer__SslCertPath`/`SslKeyPath`. - `mac-dev-up.sh` now exports the dev cert in **step 1b, before `compose up`** — the container reads it at startup, so it must exist first. - Bring-up gates on the **TLS handshake**, not just the port: a certless server still accepts TCP, which is exactly how this stays invisible. ## Verified - `Using PEM certificate from /app/certs/backend-cert.pem` / `Kestrel configured for HTTPS/WSS on port 7777` (correct branch taken, not the fallback). - Cert presented on :7777 has SHA1 `46:08:4F:CB:…:FC:A8` — byte-identical to the trusted dev cert, so the browser accepts it. - `curl --http1.1` WebSocket upgrade to `https://localhost:7777/ws` → **101 Switching Protocols**. - `game-cache-initializer` exits 0; Redis caches initialize; `game-events-handler` clean. Use `--http1.1` when testing by hand — curl negotiates HTTP/2 over ALPN by default and the upgrade returns a misleading 400. ## Follow-up worth considering `environment.development.ts` has no `gameServerUrl` while `environment.ts` has `ws://localhost:7777` and prod has `wss://gameserver.spikersoft.com`. The runtime derivation happens to produce the right value, but an explicit dev entry would remove the dependency on page protocol.
Author
Owner

Both halves merged:

  • spikersoft-backend PR #515 — --game flag adds game-cache-initializer / game-server / game-events-handler to the Mac stack, with single-node Redis, depends_on: !override to drop the cluster chain, and the trusted dev PEM mounted so :7777 actually serves wss. Bring-up now gates on the TLS handshake rather than the port, since a certless server still accepts TCP.
  • spikersoft-angular PR #610 — gameServerUrl defined explicitly (wss://localhost:7777) in environment.development.ts, and the base environment.ts corrected from ws:// to wss://; smoke spec locks it.

Verified end to end: Using PEM certificate from /app/certs/backend-cert.pem, the cert presented on :7777 is byte-identical to the trusted dev cert, and a WebSocket upgrade to /ws returns 101 Switching Protocols.

Closing.

Both halves merged: - **spikersoft-backend PR #515** — `--game` flag adds `game-cache-initializer` / `game-server` / `game-events-handler` to the Mac stack, with single-node Redis, `depends_on: !override` to drop the cluster chain, and the trusted dev PEM mounted so `:7777` actually serves `wss`. Bring-up now gates on the TLS handshake rather than the port, since a certless server still accepts TCP. - **spikersoft-angular PR #610** — `gameServerUrl` defined explicitly (`wss://localhost:7777`) in `environment.development.ts`, and the base `environment.ts` corrected from `ws://` to `wss://`; smoke spec locks it. Verified end to end: `Using PEM certificate from /app/certs/backend-cert.pem`, the cert presented on :7777 is byte-identical to the trusted dev cert, and a WebSocket upgrade to `/ws` returns 101 Switching Protocols. Closing.
Sign in to join this conversation.