mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
Add env-driven Dokploy routing
This commit is contained in:
@@ -23,6 +23,7 @@ main also auto-detects your install.
|
|||||||
| **Version key** (sent as `X-DorkNet-Version` header) | `december_2020_12_18` |
|
| **Version key** (sent as `X-DorkNet-Version` header) | `december_2020_12_18` |
|
||||||
| **Version plugin** | `DorkNet.Server/Versions/Late2020/Late2020VersionPlugin.cs` |
|
| **Version plugin** | `DorkNet.Server/Versions/Late2020/Late2020VersionPlugin.cs` |
|
||||||
| **Schema** | One EF `Initial` migration; SQLite + Postgres both work |
|
| **Schema** | One EF `Initial` migration; SQLite + Postgres both work |
|
||||||
|
| **Runtime topology** | Full API still runs from `DorkNet.Server`; microservice gateway/service hosts are scaffolded for incremental split |
|
||||||
| **Diverges from `march-2020-03-10` by** | ~170 files |
|
| **Diverges from `march-2020-03-10` by** | ~170 files |
|
||||||
|
|
||||||
Subsystems present on this branch that the March branch doesn't have:
|
Subsystems present on this branch that the March branch doesn't have:
|
||||||
@@ -58,7 +59,7 @@ cd DorkNet
|
|||||||
|
|
||||||
| Config key | Env var | Required | Notes |
|
| Config key | Env var | Required | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `Jwt:Secret` | `Jwt__Secret` | yes | ≥64 random chars |
|
| `Jwt:Secret` | `DORKNET_JWT_SECRET` or `Jwt__Secret` | yes | >=64 random chars |
|
||||||
| `Photon:AppId` | `Photon__AppId` | yes | Photon Realtime AppId (free at dashboard.photonengine.com) |
|
| `Photon:AppId` | `Photon__AppId` | yes | Photon Realtime AppId (free at dashboard.photonengine.com) |
|
||||||
| `Photon:VoiceAppId` | `Photon__VoiceAppId` | no | only if voice chat |
|
| `Photon:VoiceAppId` | `Photon__VoiceAppId` | no | only if voice chat |
|
||||||
| `Photon:CloudRegion` | `Photon__CloudRegion` | no | defaults to `us` |
|
| `Photon:CloudRegion` | `Photon__CloudRegion` | no | defaults to `us` |
|
||||||
@@ -66,6 +67,10 @@ cd DorkNet
|
|||||||
| `ConnectionStrings:Default` | `ConnectionStrings__Default` | postgres-only | full Npgsql connection string |
|
| `ConnectionStrings:Default` | `ConnectionStrings__Default` | postgres-only | full Npgsql connection string |
|
||||||
| `ConnectionStrings:Redis` | `ConnectionStrings__Redis` | no | enables SignalR backplane |
|
| `ConnectionStrings:Redis` | `ConnectionStrings__Redis` | no | enables SignalR backplane |
|
||||||
| `Domain:Apex` | `DORKNET_DOMAIN` | no | defaults to `localhost`; set for production |
|
| `Domain:Apex` | `DORKNET_DOMAIN` | no | defaults to `localhost`; set for production |
|
||||||
|
| `S3:Endpoint` | `S3__Endpoint` | production | S3-compatible API endpoint for blobs/images |
|
||||||
|
| `S3:AccessKey` | `S3__AccessKey` | production | S3 access key |
|
||||||
|
| `S3:SecretKey` | `S3__SecretKey` | production | S3 secret key |
|
||||||
|
| `S3:Region` | `S3__Region` | no | defaults to `garage`; use `auto` for R2 |
|
||||||
|
|
||||||
**4. Boot:**
|
**4. Boot:**
|
||||||
|
|
||||||
@@ -79,8 +84,8 @@ categories / playlists, and starts listening. You should see
|
|||||||
`Application started.` in the log; `curl http://localhost:8080/healthz`
|
`Application started.` in the log; `curl http://localhost:8080/healthz`
|
||||||
returns 200.
|
returns 200.
|
||||||
|
|
||||||
For a full Docker / VPS deploy, see
|
For Docker, Dokploy, and microservices-scaffold deploy notes, see
|
||||||
[main's Advanced setup guide](../../blob/main/docs/advanced-setup.md).
|
[docs/deploy.md](docs/deploy.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -196,13 +201,42 @@ Default `0` (off).
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `DorkNet.Server/` | ASP.NET Core backend (controllers, services, data, hubs, middleware) |
|
| `DorkNet.Server/` | ASP.NET Core backend (controllers, services, data, hubs, middleware) |
|
||||||
| `DorkNet.Models/` | DTOs shared between server and client mod |
|
| `DorkNet.Models/` | DTOs shared between server and client mod |
|
||||||
|
| `DorkNet.Contracts/` | Shared service names, service-map options, and health/probe contracts |
|
||||||
|
| `DorkNet.ServiceDefaults/` | Shared microservice health, JSON, HTTP client, and service identity setup |
|
||||||
|
| `DorkNet.Gateway/` | Edge/gateway host scaffold with service-map and service-health endpoints |
|
||||||
|
| `DorkNet.Services.*` | Initial identity, rooms, and notify service host scaffolds |
|
||||||
| `DorkNet.ClientMod/` | MelonLoader IL2CPP mod — the client patcher |
|
| `DorkNet.ClientMod/` | MelonLoader IL2CPP mod — the client patcher |
|
||||||
| `tools/` | Installers (`install-melon.ps1`, `install-legacy-client.ps1`, `remove-eac.ps1`), Cloudflare tunnel templates |
|
| `tools/` | Installers (`install-melon.ps1`, `install-legacy-client.ps1`, `remove-eac.ps1`), Cloudflare tunnel templates |
|
||||||
|
| `Dockerfile`, `Dockerfile.service`, `docker-compose.microservices*.yml` | Full-server, local microservices, and Dokploy microservices deployment entrypoints |
|
||||||
|
|
||||||
See [docs/architecture.md](docs/architecture.md) for the full mental
|
See [docs/architecture.md](docs/architecture.md) for the full mental
|
||||||
model (project layout, request lifecycle, watch-mirror controller
|
model (project layout, request lifecycle, watch-mirror controller
|
||||||
pattern, where to start contributing).
|
pattern, where to start contributing).
|
||||||
|
|
||||||
|
### Microservices scaffold
|
||||||
|
|
||||||
|
`docker-compose.microservices.yml` starts the new gateway, identity,
|
||||||
|
rooms, and notify service hosts plus Compose-managed Postgres and Redis
|
||||||
|
for local testing. `docker-compose.microservices.dokploy.yml` is the
|
||||||
|
Dokploy version; it reads `DORKNET_DOMAIN` and publishes the required
|
||||||
|
Traefik host rules for the gateway. These files do **not** replace the
|
||||||
|
full `DorkNet.Server` runtime yet; the public API is still served by the
|
||||||
|
monolith while domains are peeled into services incrementally.
|
||||||
|
|
||||||
|
The compose file intentionally does not start object storage. Point it at
|
||||||
|
your separate S3-compatible instance:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
$env:DORKNET_S3_ENDPOINT="https://your-s3-endpoint"
|
||||||
|
$env:DORKNET_S3_ACCESS_KEY="..."
|
||||||
|
$env:DORKNET_S3_SECRET_KEY="..."
|
||||||
|
$env:DORKNET_S3_REGION="garage" # or auto for R2
|
||||||
|
docker compose -f docker-compose.microservices.yml up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
For deployment details, including Dokploy env vars and the current
|
||||||
|
monolith-vs-microservices split, see [docs/deploy.md](docs/deploy.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Database & migrations
|
## Database & migrations
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
services:
|
||||||
|
gateway:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Gateway/DorkNet.Gateway.csproj
|
||||||
|
APP_DLL: DorkNet.Gateway.dll
|
||||||
|
environment:
|
||||||
|
DORKNET_DOMAIN: ${DORKNET_DOMAIN:?Set DORKNET_DOMAIN in Dokploy}
|
||||||
|
DorkNet__Services__Identity: http://identity:8080
|
||||||
|
DorkNet__Services__Rooms: http://rooms:8080
|
||||||
|
DorkNet__Services__Notify: http://notify:8080
|
||||||
|
DorkNet__Services__Monolith: ""
|
||||||
|
expose:
|
||||||
|
- "8080"
|
||||||
|
labels:
|
||||||
|
traefik.enable: "true"
|
||||||
|
traefik.docker.network: dokploy-network
|
||||||
|
traefik.http.routers.dorknet-gateway.entrypoints: web
|
||||||
|
traefik.http.routers.dorknet-gateway.service: dorknet-gateway
|
||||||
|
traefik.http.routers.dorknet-gateway.rule: >-
|
||||||
|
Host(`${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`www.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`admin.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`api.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`auth.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`accounts.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`bugreporting.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`cards.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`cdn.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`chat.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`clubs.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`cms.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`commerce.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`data.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`datacollection.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`discovery.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`econ.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`feed.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`gamelogs.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`geo.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`img.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`leaderboard.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`link.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`lists.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`match.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`moderation.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`notify.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`ns.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`platformnotifications.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`playersettings.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`roomcomments.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`roomieintegrations.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`rooms.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`storage.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`strings.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`strings-cdn.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`studio.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`thorn.${DORKNET_DOMAIN}`)
|
||||||
|
|| Host(`videos.${DORKNET_DOMAIN}`)
|
||||||
|
traefik.http.services.dorknet-gateway.loadbalancer.server.port: "8080"
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
- dokploy-network
|
||||||
|
depends_on:
|
||||||
|
- identity
|
||||||
|
- rooms
|
||||||
|
- notify
|
||||||
|
|
||||||
|
identity:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Identity/DorkNet.Services.Identity.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Identity.dll
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
rooms:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Rooms/DorkNet.Services.Rooms.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Rooms.dll
|
||||||
|
environment:
|
||||||
|
S3__Endpoint: ${DORKNET_S3_ENDPOINT:-}
|
||||||
|
S3__AccessKey: ${DORKNET_S3_ACCESS_KEY:-}
|
||||||
|
S3__SecretKey: ${DORKNET_S3_SECRET_KEY:-}
|
||||||
|
S3__Region: ${DORKNET_S3_REGION:-garage}
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
notify:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Notify/DorkNet.Services.Notify.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Notify.dll
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: dorknet
|
||||||
|
POSTGRES_USER: dorknet
|
||||||
|
POSTGRES_PASSWORD: dorknet_dev
|
||||||
|
volumes:
|
||||||
|
- microservices-postgres:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dokploy-network:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
microservices-postgres:
|
||||||
+49
-9
@@ -14,15 +14,27 @@ files in this folder.
|
|||||||
|
|
||||||
```
|
```
|
||||||
DorkNet.sln
|
DorkNet.sln
|
||||||
├── DorkNet.Server ASP.NET Core 9 backend — the bulk of the code
|
├── DorkNet.Server ASP.NET Core 9 backend — full public API today
|
||||||
├── DorkNet.Models Shared DTO + serialization types
|
├── DorkNet.Models Shared DTO + serialization types
|
||||||
└── DorkNet.ClientMod MelonLoader 6 IL2CPP mod — the client patcher
|
├── DorkNet.Contracts Shared service contracts for the service split
|
||||||
|
├── DorkNet.ServiceDefaults Shared service-host setup
|
||||||
|
├── DorkNet.Gateway Gateway/edge host scaffold
|
||||||
|
├── DorkNet.Services.Identity Identity service scaffold
|
||||||
|
├── DorkNet.Services.Rooms Rooms service scaffold
|
||||||
|
├── DorkNet.Services.Notify Notify service scaffold
|
||||||
|
└── DorkNet.ClientMod MelonLoader 6 IL2CPP mod — the client patcher
|
||||||
```
|
```
|
||||||
|
|
||||||
| Project | Responsibility | Target |
|
| Project | Responsibility | Target |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `DorkNet.Server` | REST API, SignalR hub, admin SPA host, static-site host, image CDN, matchmaking, persistence | .NET 9 |
|
| `DorkNet.Server` | REST API, SignalR hub, admin SPA host, static-site host, image CDN, matchmaking, persistence | .NET 9 |
|
||||||
| `DorkNet.Models` | DTOs the wire protocol speaks. No project dependencies. | .NET 9 |
|
| `DorkNet.Models` | DTOs the wire protocol speaks. No project dependencies. | .NET 9 |
|
||||||
|
| `DorkNet.Contracts` | Service names, service-map options, health responses, probe responses | .NET 9 |
|
||||||
|
| `DorkNet.ServiceDefaults` | Common microservice health checks, JSON defaults, HTTP clients, service identity | .NET 9 |
|
||||||
|
| `DorkNet.Gateway` | Edge/gateway scaffold; exposes internal service map and service health probes | .NET 9 |
|
||||||
|
| `DorkNet.Services.Identity` | Future home for accounts, auth, platform login, JWT issuance | .NET 9 |
|
||||||
|
| `DorkNet.Services.Rooms` | Future home for rooms, room keys, matchmaking, discovery | .NET 9 |
|
||||||
|
| `DorkNet.Services.Notify` | Future home for SignalR edge, notification fan-out, presence, request logs | .NET 9 |
|
||||||
| `DorkNet.ClientMod` | MelonLoader 0.6.x IL2CPP mod — the client patcher, JSON config | .NET 6 |
|
| `DorkNet.ClientMod` | MelonLoader 0.6.x IL2CPP mod — the client patcher, JSON config | .NET 6 |
|
||||||
|
|
||||||
`ClientMod` applies the client-side patches needed to point the 2020
|
`ClientMod` applies the client-side patches needed to point the 2020
|
||||||
@@ -48,9 +60,38 @@ DorkNet.Server/
|
|||||||
├── admin-ui/ Vite + React SPA source — builds to wwwroot/admin/
|
├── admin-ui/ Vite + React SPA source — builds to wwwroot/admin/
|
||||||
├── site/ Public website Vite SPA source — builds to wwwroot/site/
|
├── site/ Public website Vite SPA source — builds to wwwroot/site/
|
||||||
├── wwwroot/ Static file root, with per-host subdirs (admin/, site/, feed/)
|
├── wwwroot/ Static file root, with per-host subdirs (admin/, site/, feed/)
|
||||||
└── Program.cs Host + DI + middleware pipeline (split candidates noted at the bottom)
|
├── Startup/ Host composition, DI registration, database bootstrap, middleware pipeline
|
||||||
|
└── Program.cs Short composition root
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Runtime topology
|
||||||
|
|
||||||
|
`DorkNet.Server` is still the only production-complete runtime. It owns
|
||||||
|
the full public Rec Room-compatible HTTP surface, SignalR hub, static
|
||||||
|
hosts, database bootstrap, Redis-backed ephemeral state, and S3-backed
|
||||||
|
blob storage.
|
||||||
|
|
||||||
|
The microservice projects are the migration scaffold. They are wired into
|
||||||
|
the solution and Docker Compose, but they only expose internal health and
|
||||||
|
capability endpoints today:
|
||||||
|
|
||||||
|
- `DorkNet.Gateway` exposes `/internal/services` and
|
||||||
|
`/internal/services/health`.
|
||||||
|
- `DorkNet.Services.Identity` exposes
|
||||||
|
`/internal/identity/capabilities`.
|
||||||
|
- `DorkNet.Services.Rooms` exposes `/internal/rooms/capabilities`.
|
||||||
|
- `DorkNet.Services.Notify` exposes `/internal/notify/capabilities`.
|
||||||
|
|
||||||
|
`docker-compose.microservices.yml` starts gateway, identity, rooms, and
|
||||||
|
notify alongside Compose-managed Postgres and Redis for local testing.
|
||||||
|
`docker-compose.microservices.dokploy.yml` is the Dokploy shape: it puts
|
||||||
|
the gateway on `dokploy-network` and builds exact Traefik host rules from
|
||||||
|
`DORKNET_DOMAIN`. Object storage is external S3-compatible storage
|
||||||
|
configured with `DORKNET_S3_*` environment variables; the compose files
|
||||||
|
do not run MinIO or Garage. The next migration step is to move one public
|
||||||
|
route group at a time behind the gateway while preserving existing URLs
|
||||||
|
and response shapes.
|
||||||
|
|
||||||
### Controllers/ groupings
|
### Controllers/ groupings
|
||||||
|
|
||||||
20 top-level groups, each responsible for one product surface the
|
20 top-level groups, each responsible for one product surface the
|
||||||
@@ -236,11 +277,10 @@ worse.
|
|||||||
intentional — the decompiled source is provenance only, not
|
intentional — the decompiled source is provenance only, not
|
||||||
shippable. Comments give a future contributor a breadcrumb to
|
shippable. Comments give a future contributor a breadcrumb to
|
||||||
re-derive the wire shape if the decompile changes.
|
re-derive the wire shape if the decompile changes.
|
||||||
- **`Program.cs` is long.** Most of it is one-time schema patching
|
- **Microservices are scaffolded, not fully cut over.** The gateway and
|
||||||
(`RunPatchAsync` calls for the Postgres `EnsureCreated` path) that's
|
service hosts compile and run, but the public API still lives in
|
||||||
pending consolidation per [`Data/MIGRATIONS.md`](../DorkNet.Server/Data/MIGRATIONS.md).
|
`DorkNet.Server`. Move endpoints by domain slice, not by copying random
|
||||||
Once that lands, the remaining ~500 lines of middleware + host
|
controllers.
|
||||||
setup will be split into `Startup/*` extensions.
|
|
||||||
- **Two-provider EF.** SQLite and Postgres each have their own
|
- **Two-provider EF.** SQLite and Postgres each have their own
|
||||||
EnsureCreated/Migrate dance because some Postgres-only column types
|
EnsureCreated/Migrate dance because some Postgres-only column types
|
||||||
drift the model snapshot. The split is annoying but lets one
|
drift the model snapshot. The split is annoying but lets one
|
||||||
|
|||||||
+261
-228
@@ -1,280 +1,313 @@
|
|||||||
# DorkNet — Coolify deployment
|
# DorkNet Deployment
|
||||||
|
|
||||||
This doc covers the production cutover from the local-machine setup
|
This branch has two deployable shapes:
|
||||||
(SQLite + Kestrel direct + mkcert + per-machine cloudflared) to the
|
|
||||||
horizontally-scaled Coolify deployment (Postgres + Redis + Garage S3 +
|
|
||||||
2-replica Kestrel behind cloudflared, all on the same Coolify project).
|
|
||||||
|
|
||||||
The plan-of-record is `BUILD_PROPER_PLAN.md` at the repo root and the
|
- **Production/full API:** build the root `Dockerfile`, which runs
|
||||||
phased writeup at `~/.claude/plans/pahese-0-1-horizontal-cheeky-hickey.md`.
|
`DorkNet.Server`. This is still the runtime that serves every public
|
||||||
Anything that contradicts this doc, that doc wins.
|
Rec Room-compatible endpoint.
|
||||||
|
- **Microservices scaffold:** deploy `docker-compose.microservices.yml`
|
||||||
|
for local testing or `docker-compose.microservices.dokploy.yml` on
|
||||||
|
Dokploy. Both start `DorkNet.Gateway`,
|
||||||
|
`DorkNet.Services.Identity`, `DorkNet.Services.Rooms`,
|
||||||
|
`DorkNet.Services.Notify`, Postgres, and Redis. These hosts currently
|
||||||
|
expose internal health/capability endpoints only; public routes move
|
||||||
|
behind the gateway incrementally.
|
||||||
|
|
||||||
|
Object storage is always a **separate S3-compatible instance**. The
|
||||||
|
microservices compose files do not start MinIO/Garage.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Coolify project layout
|
## Dokploy: full server
|
||||||
|
|
||||||
One Coolify *project* with these services:
|
Use this for a real server today.
|
||||||
|
|
||||||
| Service | Image / source | Notes |
|
Create a Dokploy **Application**:
|
||||||
| -------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
||||||
| `dorknet-server` | Build from this repo's `Dockerfile` | Replicas: **2** to start. Internal port 8080. No public domain. |
|
|
||||||
| `dorknet-postgres` | Coolify one-click **Postgres** | Persistent volume on Coolify's data store. |
|
|
||||||
| `dorknet-redis` | Coolify one-click **Redis** | Persistent volume optional; used for ephemeral state + SignalR backplane. |
|
|
||||||
| `dorknet-garage` | Coolify one-click **Garage** | S3-compatible. Persistent volume (room blobs are 100s of MB). |
|
|
||||||
| `dorknet-cloudflared`| Cloudflare's `cloudflared` image, custom config | Reads `tools/cloudflared-config.coolify.yml.template` rendered with a tunnel ID. |
|
|
||||||
|
|
||||||
All five sit on the same project network so the server reaches its
|
| Field | Value |
|
||||||
dependencies over Coolify's internal Docker DNS.
|
|---|---|
|
||||||
|
| Source | This GitHub repo / branch |
|
||||||
|
| Build type | Dockerfile |
|
||||||
|
| Dockerfile path | `Dockerfile` |
|
||||||
|
| Context | `.` |
|
||||||
|
| Container port | `8080` |
|
||||||
|
|
||||||
## Required environment variables on `dorknet-server`
|
Set application environment variables:
|
||||||
|
|
||||||
Set these in Coolify's *Environment Variables* tab. Coolify expands
|
```env
|
||||||
`${...}` references to other services in the same project, so the
|
|
||||||
connection-string lookups link automatically.
|
|
||||||
|
|
||||||
```
|
|
||||||
ASPNETCORE_ENVIRONMENT=Production
|
ASPNETCORE_ENVIRONMENT=Production
|
||||||
|
ASPNETCORE_URLS=http://0.0.0.0:8080
|
||||||
|
DOTNET_RUNNING_IN_CONTAINER=true
|
||||||
|
|
||||||
# Database
|
DORKNET_DOMAIN=yourdomain.com
|
||||||
ConnectionStrings__Default=Host=dorknet-postgres;Port=5432;Database=dorknet;Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}
|
DORKNET_JWT_SECRET=replace-with-at-least-64-random-characters
|
||||||
|
|
||||||
# Redis
|
Database__Provider=postgres
|
||||||
ConnectionStrings__Redis=dorknet-redis:6379
|
ConnectionStrings__Default=Host=<postgres-host>;Port=5432;Database=dorknet;Username=<user>;Password=<password>
|
||||||
|
ConnectionStrings__Redis=redis://<redis-host>:6379
|
||||||
|
|
||||||
# JWT
|
S3__Endpoint=https://your-s3-api-endpoint
|
||||||
DORKNET_JWT_SECRET=<openssl rand -base64 64>
|
S3__AccessKey=your-access-key
|
||||||
|
S3__SecretKey=your-secret-key
|
||||||
# Object storage (Garage). Bucket names are hard-coded in code; only
|
|
||||||
# the endpoint + credentials need configuring. Objects are organized
|
|
||||||
# under owner-scoped keys inside each bucket, e.g.
|
|
||||||
# players/{playerId}/camera, players/{playerId}/profile,
|
|
||||||
# players/{playerId}/rooms/{roomId}, players/{playerId}/dorm.
|
|
||||||
S3__Endpoint=http://dorknet-garage:3900
|
|
||||||
S3__Region=garage
|
S3__Region=garage
|
||||||
S3__AccessKey=<garage admin key>
|
|
||||||
S3__SecretKey=<garage admin secret>
|
|
||||||
|
|
||||||
# Image CDN signing. The 2020 client verifies Content-Signature on
|
Photon__AppId=your-photon-realtime-app-id
|
||||||
# image downloads. Use the private half of the key whose public modulus
|
Photon__VoiceAppId=your-photon-voice-app-id
|
||||||
# the client knows for p1. Store PEM newlines as \n if your env UI is
|
Photon__CloudRegion=eu
|
||||||
# single-line, or use DORKNET_IMAGE_SIGNING_PRIVATE_KEY_BASE64.
|
|
||||||
DORKNET_IMAGE_SIGNING_KEY_ID=p1
|
|
||||||
DORKNET_IMAGE_SIGNING_PRIVATE_KEY=<PEM RSA private key>
|
|
||||||
|
|
||||||
# Photon (copy from local appsettings.json — same AppId across envs)
|
|
||||||
Photon__AppId=<your-photon-realtime-app-id>
|
|
||||||
Photon__VoiceAppId=1917ff4a-b7ad-4ad6-9ea2-9642bc174b70
|
|
||||||
Photon__CloudRegion=us
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cloudflare R2 instead of Garage
|
If Postgres and Redis are Dokploy-managed services in the same project,
|
||||||
|
use the internal service hostnames Dokploy gives you. For a Compose-style
|
||||||
DorkNet can use Cloudflare R2 without code changes because R2 speaks the
|
network the values usually look like:
|
||||||
S3-compatible API. Create these three R2 buckets first:
|
|
||||||
|
|
||||||
|
```env
|
||||||
|
ConnectionStrings__Default=Host=postgres;Port=5432;Database=dorknet;Username=dorknet;Password=dorknet_dev
|
||||||
|
ConnectionStrings__Redis=redis://redis:6379
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Domains
|
||||||
|
|
||||||
|
Point DNS at the Dokploy server, then add domains to the same
|
||||||
|
application. Every host routes to container port `8080`.
|
||||||
|
|
||||||
|
Add these hosts for full client compatibility:
|
||||||
|
|
||||||
|
```text
|
||||||
|
yourdomain.com
|
||||||
|
www.yourdomain.com
|
||||||
|
admin.yourdomain.com
|
||||||
|
api.yourdomain.com
|
||||||
|
auth.yourdomain.com
|
||||||
|
accounts.yourdomain.com
|
||||||
|
bugreporting.yourdomain.com
|
||||||
|
cards.yourdomain.com
|
||||||
|
cdn.yourdomain.com
|
||||||
|
chat.yourdomain.com
|
||||||
|
clubs.yourdomain.com
|
||||||
|
cms.yourdomain.com
|
||||||
|
commerce.yourdomain.com
|
||||||
|
data.yourdomain.com
|
||||||
|
datacollection.yourdomain.com
|
||||||
|
discovery.yourdomain.com
|
||||||
|
econ.yourdomain.com
|
||||||
|
feed.yourdomain.com
|
||||||
|
gamelogs.yourdomain.com
|
||||||
|
geo.yourdomain.com
|
||||||
|
img.yourdomain.com
|
||||||
|
leaderboard.yourdomain.com
|
||||||
|
link.yourdomain.com
|
||||||
|
lists.yourdomain.com
|
||||||
|
match.yourdomain.com
|
||||||
|
moderation.yourdomain.com
|
||||||
|
notify.yourdomain.com
|
||||||
|
ns.yourdomain.com
|
||||||
|
platformnotifications.yourdomain.com
|
||||||
|
playersettings.yourdomain.com
|
||||||
|
roomcomments.yourdomain.com
|
||||||
|
roomieintegrations.yourdomain.com
|
||||||
|
rooms.yourdomain.com
|
||||||
|
storage.yourdomain.com
|
||||||
|
strings.yourdomain.com
|
||||||
|
strings-cdn.yourdomain.com
|
||||||
|
studio.yourdomain.com
|
||||||
|
thorn.yourdomain.com
|
||||||
|
videos.yourdomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
All of these point to the same `DorkNet.Server` application/container
|
||||||
|
port today. `admin` and `feed` serve static hosts; the rest are the
|
||||||
|
service URLs returned to the client by `ConfigService`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dokploy: microservices scaffold
|
||||||
|
|
||||||
|
Use this to run the new gateway/service-host layout. It is not yet a
|
||||||
|
replacement for the full public API.
|
||||||
|
|
||||||
|
Create a Dokploy **Compose** service from one of these files:
|
||||||
|
|
||||||
|
| Compose file | Use |
|
||||||
|
|---|---|
|
||||||
|
| `docker-compose.microservices.yml` | Local/dev-style compose; use Dokploy's domain UI manually |
|
||||||
|
| `docker-compose.microservices.dokploy.yml` | Dokploy Traefik labels generated from `DORKNET_DOMAIN` |
|
||||||
|
|
||||||
|
Both compose files provide:
|
||||||
|
|
||||||
|
| Service | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `gateway` | Edge host scaffold; service map and service health probes |
|
||||||
|
| `identity` | Future identity/auth/accounts service |
|
||||||
|
| `rooms` | Future rooms/matchmaking/discovery service |
|
||||||
|
| `notify` | Future notification/presence service |
|
||||||
|
| `postgres` | Internal Postgres for the service network |
|
||||||
|
| `redis` | Internal Redis for ephemeral state and fan-out |
|
||||||
|
|
||||||
|
Only the gateway needs a public domain during scaffold testing. Route the
|
||||||
|
gateway domain to service `gateway`, container port `8080`.
|
||||||
|
|
||||||
|
In Dokploy's Compose domain modal:
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|---|---|
|
||||||
|
| Service Name | `gateway` |
|
||||||
|
| Host | the domain you are adding |
|
||||||
|
| Path | `/` |
|
||||||
|
| Internal Path | `/` |
|
||||||
|
| Container Port | `8080` |
|
||||||
|
| Strip Path | off |
|
||||||
|
|
||||||
|
If the service dropdown says **Services not found**, deploy or redeploy
|
||||||
|
the Compose service first, then refresh the domain modal. Dokploy derives
|
||||||
|
the service list from the parsed compose file.
|
||||||
|
|
||||||
|
### Env-driven domains
|
||||||
|
|
||||||
|
Use `docker-compose.microservices.dokploy.yml` if you do not want to add
|
||||||
|
every domain row manually in Dokploy. Set:
|
||||||
|
|
||||||
|
```env
|
||||||
|
DORKNET_DOMAIN=yourdomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
That file adds Traefik labels to `gateway` for the apex plus every host
|
||||||
|
listed in the full-server domain list above, then sends them all to
|
||||||
|
container port `8080`. It also attaches the gateway to Dokploy's external
|
||||||
|
`dokploy-network`, which is why this is a separate file instead of the
|
||||||
|
default local compose file.
|
||||||
|
|
||||||
|
DNS still has to resolve to the Dokploy server. The simplest setup is an
|
||||||
|
apex record plus wildcard subdomain record:
|
||||||
|
|
||||||
|
```text
|
||||||
|
yourdomain.com A/AAAA <dokploy-server-ip>
|
||||||
|
*.yourdomain.com A/AAAA <dokploy-server-ip>
|
||||||
|
```
|
||||||
|
|
||||||
|
If you do not use wildcard DNS, add A/AAAA or CNAME records for each
|
||||||
|
listed subdomain.
|
||||||
|
|
||||||
|
When the gateway starts carrying the public API, every public DorkNet
|
||||||
|
domain listed above should point to `gateway`. Until then, keep the real
|
||||||
|
player-facing domains on the full `DorkNet.Server` application.
|
||||||
|
|
||||||
|
Set these Compose environment variables in Dokploy:
|
||||||
|
|
||||||
|
```env
|
||||||
|
DORKNET_S3_ENDPOINT=https://your-s3-api-endpoint
|
||||||
|
DORKNET_S3_ACCESS_KEY=your-access-key
|
||||||
|
DORKNET_S3_SECRET_KEY=your-secret-key
|
||||||
|
DORKNET_S3_REGION=garage
|
||||||
|
```
|
||||||
|
|
||||||
|
The compose services already refer to Postgres as `postgres` and Redis as
|
||||||
|
`redis` on the internal Compose network. Do not add an `object-storage`
|
||||||
|
service unless you intentionally want a local dev-only S3 emulator.
|
||||||
|
|
||||||
|
Useful smoke checks after deploy:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl https://gateway.yourdomain.com/healthz
|
||||||
|
curl https://gateway.yourdomain.com/internal/services
|
||||||
|
curl https://gateway.yourdomain.com/internal/services/health
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected during scaffold-only deploys: `/internal/services/health`
|
||||||
|
reports gateway-visible status for identity, rooms, and notify. It does
|
||||||
|
not prove the full game API has moved yet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## S3 storage
|
||||||
|
|
||||||
|
DorkNet uses S3-compatible storage for room blobs, profile images,
|
||||||
|
camera photos, and CDN/image pipeline backing data. Create these buckets
|
||||||
|
before production cutover:
|
||||||
|
|
||||||
|
```text
|
||||||
profile-images
|
profile-images
|
||||||
camera-photos
|
camera-photos
|
||||||
room-blobs
|
room-blobs
|
||||||
```
|
```
|
||||||
|
|
||||||
Then create an R2 API token with Object Read & Write access to both
|
Garage-style endpoints usually use:
|
||||||
buckets and set the server env vars:
|
|
||||||
|
|
||||||
|
```env
|
||||||
|
S3__Endpoint=https://your-garage-or-s3-api
|
||||||
|
S3__Region=garage
|
||||||
|
S3__AccessKey=...
|
||||||
|
S3__SecretKey=...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Cloudflare R2 uses the S3 API endpoint, not a public bucket URL:
|
||||||
|
|
||||||
|
```env
|
||||||
S3__Endpoint=https://<cloudflare-account-id>.r2.cloudflarestorage.com
|
S3__Endpoint=https://<cloudflare-account-id>.r2.cloudflarestorage.com
|
||||||
S3__Region=auto
|
S3__Region=auto
|
||||||
S3__AccessKey=<r2 access key id>
|
S3__AccessKey=<r2 access key id>
|
||||||
S3__SecretKey=<r2 secret access key>
|
S3__SecretKey=<r2 secret access key>
|
||||||
S3__MaxErrorRetry=1
|
S3__MaxErrorRetry=1
|
||||||
S3__TimeoutSeconds=8
|
S3__TimeoutSeconds=300
|
||||||
```
|
```
|
||||||
|
|
||||||
Do not use an R2 public bucket URL or custom domain for `S3__Endpoint`.
|
For the microservices compose file, use the `DORKNET_S3_*` equivalents;
|
||||||
That value must be the S3 API endpoint ending in
|
Compose maps them into `S3__*` for the rooms service.
|
||||||
`.r2.cloudflarestorage.com`.
|
|
||||||
DorkNet disables S3 chunked upload encoding in code because R2 rejects
|
|
||||||
`STREAMING-AWS4-HMAC-SHA256-PAYLOAD` request bodies.
|
|
||||||
|
|
||||||
`appsettings.Production.json` already pins `Database:Provider=postgres`
|
---
|
||||||
and binds Kestrel to `http://0.0.0.0:8080`, so those don't need to be
|
|
||||||
set as env vars.
|
|
||||||
|
|
||||||
## First-time cutover (stop-the-world)
|
## First-time cutover from SQLite
|
||||||
|
|
||||||
1. **Drain the local server.** Tell players, take it down. The cutover
|
1. Stop the local server so the SQLite source stops accepting writes.
|
||||||
is non-incremental — once the SQLite source is read, no further
|
2. Snapshot the SQLite database.
|
||||||
writes can be accepted there.
|
3. Provision Postgres, Redis, and the separate S3-compatible storage.
|
||||||
|
4. Run the migration/import tooling against the new Postgres and S3
|
||||||
|
targets.
|
||||||
|
5. Deploy the full `DorkNet.Server` application.
|
||||||
|
6. Verify `/healthz` returns 200 before sending players to the new host.
|
||||||
|
|
||||||
2. **Snapshot SQLite.** `tools/snapshot-db.ps1` writes a timestamped
|
The server uses an advisory-lock guarded startup path for Postgres so
|
||||||
copy. Keep it indefinitely as the pre-cutover baseline.
|
multiple replicas can start without racing schema/bootstrap work.
|
||||||
|
|
||||||
3. **Deploy the Coolify project** with the four services above
|
Rollback before players write to Postgres is simple: stop the deploy and
|
||||||
(Postgres / Redis / Garage / cloudflared) but the server replicas
|
return to the SQLite snapshot. After players write to Postgres, rollback
|
||||||
set to 0 — we want the database empty and idle while the migrator
|
means losing post-cutover writes unless you migrate them back.
|
||||||
runs.
|
|
||||||
|
|
||||||
4. **Run the migrator** from the operator's laptop, pointed at the
|
---
|
||||||
newly-provisioned Postgres + Garage:
|
|
||||||
|
|
||||||
```pwsh
|
## Photon Cloud
|
||||||
dotnet run --project Tools/MigrateSqliteToPostgres -- `
|
|
||||||
--sqlite ./snapshots/dorknet-pre-cutover.db `
|
|
||||||
--postgres "Host=<coolify-postgres-public-host>;Port=5432;Database=dorknet;Username=...;Password=...;SSL Mode=Require" `
|
|
||||||
--reset `
|
|
||||||
--upload-blobs `
|
|
||||||
--s3-endpoint http://<coolify-garage-public-host>:3900 `
|
|
||||||
--s3-bucket room-blobs `
|
|
||||||
--s3-access-key ... `
|
|
||||||
--s3-secret-key ...
|
|
||||||
```
|
|
||||||
|
|
||||||
The migrator runs the 9-step verification + an optional 10th step
|
Use your own Photon AppIds. Rec Room's original Photon AppIds reject
|
||||||
that bulk-uploads every `RoomDataBlobs.Bytes` row to S3, idempotent
|
external clients because their dashboard points CustomAuth at the
|
||||||
on re-run. A non-zero exit means **abort**: leave the destination
|
official backend.
|
||||||
in its dirty state, fix the diff, re-run with `--reset` for a clean
|
|
||||||
slate.
|
|
||||||
|
|
||||||
5. **Scale `dorknet-server` to 2 replicas.** First boot: one replica
|
1. Create a Photon Realtime app at <https://dashboard.photonengine.com>.
|
||||||
wins the `pg_advisory_xact_lock` and runs `EnsureCreated` (no-op,
|
2. Create a Photon Voice app if voice chat is needed.
|
||||||
schema already there from the migrator) + the seed steps; the other
|
3. Set:
|
||||||
blocks on the lock until the first commits, sees the schema is
|
|
||||||
complete, and proceeds.
|
|
||||||
|
|
||||||
6. **Verify `/healthz` is 200** on both replicas (Coolify shows this
|
```env
|
||||||
in the Container tab) before pointing the tunnel at the service.
|
Photon__AppId=<your realtime AppId>
|
||||||
|
Photon__VoiceAppId=<your voice AppId>
|
||||||
7. **Bring up `dorknet-cloudflared`** with the rendered config (see
|
Photon__CloudRegion=eu
|
||||||
`tools/cloudflared-config.coolify.yml.template`). Confirm `dig` /
|
|
||||||
`curl` against `https://api.localhost/api/health/v1` returns 200.
|
|
||||||
|
|
||||||
8. **Tell players it's back.** Done.
|
|
||||||
|
|
||||||
## Notes on multi-replica behaviour
|
|
||||||
|
|
||||||
* **Sticky sessions are no longer required.** Auth's
|
|
||||||
`OrphanAccountTracker` writes the pending claim to Redis, so a
|
|
||||||
player whose two HTTP requests hit different replicas still sees
|
|
||||||
the same account-creation state.
|
|
||||||
|
|
||||||
* **SignalR groups fan out across replicas.** The Redis backplane is
|
|
||||||
registered under the channel prefix `dorknet-signalr`; both replicas
|
|
||||||
publish/subscribe to the same channels, so a notification raised on
|
|
||||||
replica B reaches a player connected to replica A.
|
|
||||||
|
|
||||||
* **Player presence is shared.** `PlayerPresenceService` writes
|
|
||||||
`presence:player:{id}` keys to Redis with a 45 s TTL (3× heartbeat).
|
|
||||||
Either replica can answer "where is player N?".
|
|
||||||
|
|
||||||
* **Game session ids are allocated by Postgres.** `GameSessionService`
|
|
||||||
uses a `bigserial` so two replicas can `JoinOrCreate` simultaneously
|
|
||||||
without colliding.
|
|
||||||
|
|
||||||
* **Private instances live in Postgres.** Cross-replica visibility is
|
|
||||||
automatic — both replicas read from the same `PrivateInstances`
|
|
||||||
table.
|
|
||||||
|
|
||||||
## Rollback
|
|
||||||
|
|
||||||
If the cutover goes sideways before step 5 (no replicas running yet),
|
|
||||||
just delete the Postgres / Redis / Garage volumes in Coolify, set the
|
|
||||||
server replica count back to 0, and bring the local SQLite server back
|
|
||||||
up. The pre-cutover snapshot is still untouched.
|
|
||||||
|
|
||||||
After step 5, rolling back means **restoring the SQLite snapshot**
|
|
||||||
(any data players wrote on Postgres post-cutover will be lost). Be
|
|
||||||
sure the cutover validation in step 6 is convincing before you tell
|
|
||||||
players it's safe.
|
|
||||||
|
|
||||||
## Photon Cloud — own AppId + CustomAuth callback
|
|
||||||
|
|
||||||
The hard-coded RecRoom Photon AppId rejects external clients (NameServer
|
|
||||||
error code 32736 / "No auth request during expected wait time") because
|
|
||||||
their dashboard has CustomAuth set to "Reject if Auth Failed" pointing
|
|
||||||
at RecRoom's own backend. Solution: register your own free AppId.
|
|
||||||
|
|
||||||
1. Go to https://dashboard.photonengine.com → sign up → **Create a New App**
|
|
||||||
→ "Photon Realtime" → save. Repeat for "Photon Voice" if you want voice.
|
|
||||||
2. Copy each **AppId** (UUID) and set in Coolify env vars:
|
|
||||||
```
|
|
||||||
Photon__AppId=<your realtime AppId>
|
|
||||||
Photon__VoiceAppId=<your voice AppId>
|
|
||||||
Photon__CloudRegion=us
|
|
||||||
```
|
|
||||||
3. Restart the server.
|
|
||||||
|
|
||||||
That alone gets multiplayer working — the Photon NameServer accepts the
|
|
||||||
client because no CustomAuth is required.
|
|
||||||
|
|
||||||
### (Optional) wire up server-side single-session enforcement
|
|
||||||
|
|
||||||
Server already exposes `https://auth.localhost/photon/customauth` — see
|
|
||||||
[PhotonCustomAuthController.cs](DorkNet.Server/Controllers/Auth/PhotonCustomAuthController.cs).
|
|
||||||
It accepts `userid` + `LoginLock` and validates them against
|
|
||||||
`PlayerPresenceService` (Redis). Enable it in the dashboard:
|
|
||||||
|
|
||||||
1. Photon dashboard → your app → **Manage** → **Custom Authentication** tab.
|
|
||||||
2. **Enable Custom Authentication**.
|
|
||||||
3. URL: `https://auth.localhost/photon/customauth?userid={userid}&LoginLock={LoginLock}`
|
|
||||||
4. **Reject if Auth Failed**: **OFF for now** (see caveat below).
|
|
||||||
5. Save.
|
|
||||||
|
|
||||||
**Caveat — current 2020 watch doesn't send AuthValues**. A sweep of
|
|
||||||
`Cpp2IL_ISIL/IsilDump/Assembly-CSharp/` turns up no caller of
|
|
||||||
`PhotonNetwork.set_AuthValues` outside Photon's own library code, so
|
|
||||||
the unmodified client connects without supplying `{userid, LoginLock}`.
|
|
||||||
With "Reject if Auth Failed" ON, every Photon connect would fail with
|
|
||||||
NameServer 32736 — same symptom we tried to solve. With it OFF, the
|
|
||||||
client connects fine, the endpoint exists for future client patching.
|
|
||||||
|
|
||||||
### Patching the watch to send AuthValues — `DorkNet.ClientMod/`
|
|
||||||
|
|
||||||
The vanilla 2020 watch never sets `PhotonNetwork.AuthValues` (verified
|
|
||||||
by an exhaustive grep across both `Assembly-CSharp.dll` and the
|
|
||||||
Photon `Assembly-CSharp-firstpass.dll` library). To make Photon
|
|
||||||
forward `userid` + `LoginLock` to our `/photon/customauth` endpoint a
|
|
||||||
Harmony patch on the watch's Photon auth path has to attach the values
|
|
||||||
in flight. That hook lives in the **MelonLoader IL2CPP mod**
|
|
||||||
(`DorkNet.ClientMod`); the AuthValues injector itself is currently
|
|
||||||
parked (see `DorkNet.ClientMod/attic/AuthValuesInjector.cs.attic` and
|
|
||||||
the notes in `Mod.cs`) since the `/photon/customauth` endpoint runs in
|
|
||||||
permissive mode.
|
|
||||||
|
|
||||||
Install the mod on a tester's machine with:
|
|
||||||
|
|
||||||
```pwsh
|
|
||||||
.\tools\install-melon.ps1 `
|
|
||||||
-RecRoomPath "C:\…\RecRoom\Recroom_Release_Data" `
|
|
||||||
-PhotonAppId cb0880d9-…
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The script unpacks MelonLoader, builds `DorkNet.ClientMod.dll`, and
|
Server-side Photon custom auth exists at `/photon/customauth`, but the
|
||||||
drops it into the client's `Mods/` folder. On the first run it prints
|
2020 client does not send the needed AuthValues without client patching.
|
||||||
"launch the game once" so MelonLoader can generate its IL2CPP
|
Leave Photon dashboard "Reject if Auth Failed" off unless the client mod
|
||||||
assemblies; relaunch the script with `-ResumeBuild` to finish.
|
is updated to send `userid` and `LoginLock`.
|
||||||
|
|
||||||
Once AuthValues injection is re-enabled in the mod and in production:
|
---
|
||||||
|
|
||||||
1. Flip [`PhotonCustomAuthController`](../DorkNet.Server/Controllers/Auth/PhotonCustomAuthController.cs)
|
|
||||||
from permissive-mode (`ResultCode 1` always) back to strict —
|
|
||||||
parse `userid` + `LoginLock`, validate against
|
|
||||||
`PlayerPresenceService.ValidateLock`, return `ResultCode 3` on
|
|
||||||
mismatch.
|
|
||||||
2. In the Photon dashboard, flip **"Reject if Auth Failed"** ON.
|
|
||||||
3. Duplicate-account login from a different LoginLock now fails
|
|
||||||
Photon auth at the NameServer — never reaches the dorm.
|
|
||||||
|
|
||||||
## Future schema changes
|
## Future schema changes
|
||||||
|
|
||||||
PR 4 keeps `EnsureCreated` for the first deploy (the migrator builds
|
SQLite dev DBs apply EF migrations. The production Postgres path is still
|
||||||
the schema directly, then EnsureCreated is a no-op the server can do
|
based on `EnsureCreated()` plus idempotent bootstrap steps. Until that is
|
||||||
under advisory-lock to be safe on multi-replica boot). Any
|
changed to `Migrate()` under the same advisory lock, treat production
|
||||||
**post-cutover** schema change needs a real Postgres migration:
|
schema changes as explicit deployment work:
|
||||||
|
|
||||||
1. `dotnet ef migrations add <Name> --project DorkNet.Server`
|
1. Add or update the entity model.
|
||||||
2. Replace the `EnsureCreated` call in `Program.cs` with
|
2. Add the migration for SQLite/dev.
|
||||||
`db.Database.Migrate()` *inside* the same advisory-lock block.
|
3. Add any needed idempotent Postgres bootstrap SQL.
|
||||||
3. Deploy. Both replicas race for the lock, the winner runs the
|
4. Deploy and verify `/healthz`.
|
||||||
migration, the loser sees the new history row and skips.
|
|
||||||
|
|
||||||
Until that swap happens, treat the schema as frozen.
|
See [`../DorkNet.Server/Data/MIGRATIONS.md`](../DorkNet.Server/Data/MIGRATIONS.md)
|
||||||
|
for the detailed migration discipline.
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ visitor → https://api.localhost/api/versioncheck/v4
|
|||||||
→ cloudflared tunnel
|
→ cloudflared tunnel
|
||||||
→ cloudflared (running on your machine)
|
→ cloudflared (running on your machine)
|
||||||
→ https://localhost:443 (Host: api.localhost, mkcert cert)
|
→ https://localhost:443 (Host: api.localhost, mkcert cert)
|
||||||
→ DorkNet.Server matches [Host("api.localhost")] → handler runs
|
→ DorkNet.Server HostFiltering + subdomain-aware handlers
|
||||||
```
|
```
|
||||||
|
|
||||||
The leg from cloudflared → localhost has `noTLSVerify: true` because
|
The leg from cloudflared → localhost has `noTLSVerify: true` because
|
||||||
|
|||||||
Reference in New Issue
Block a user