added path variable

This commit is contained in:
2026-10-23 19:38:36 +02:00
parent 19428550ca
commit 0913adaca5
9 changed files with 61 additions and 13 deletions
+17
View File
@@ -0,0 +1,17 @@
# Copy to .env (next to docker-compose.yml) and adjust. Docker Compose reads .env automatically.
# Full URL the browser uses to reach the game hub through the reverse proxy.
# Empty = "<website origin>/hub".
SERVER_URL=https://game.example.com/server/hub
# Prefix the game server should accept when the proxy forwards its sub-path without
# stripping it (e.g. /server). Leave empty if the proxy strips the prefix.
PATH_BASE=
# Comma separated origins allowed to connect to the hub. Empty = allow any origin.
ALLOWED_ORIGINS=https://game.example.com
# Optional gameplay tuning, passed to the server as Game__<Setting> (see GameSettings.cs).
# Add these under the server's "environment:" in docker-compose.yml to use them.
# Game__ItemSpawnChance=0.1
# Game__CountdownSeconds=3
+1
View File
@@ -0,0 +1 @@
.env
+3
View File
@@ -37,6 +37,8 @@ docker network create actionBlocks_server
docker compose up -d --build docker compose up -d --build
``` ```
The website uses relative URLs. It works under a sub-path such as `/website/`, whether or not the proxy strips that prefix.
The reverse proxy (attached to both networks) should route: The reverse proxy (attached to both networks) should route:
- the site to `actionblocks-website:80` - the site to `actionblocks-website:80`
@@ -47,6 +49,7 @@ The reverse proxy (attached to both networks) should route:
| `SERVER_URL` | website | Hub URL as seen by the browser. Empty means `<site origin>/hub`. Written into `config.js` at container start. | | `SERVER_URL` | website | Hub URL as seen by the browser. Empty means `<site origin>/hub`. Written into `config.js` at container start. |
| `ALLOWED_ORIGINS` | server | Comma-separated CORS origins. Empty allows any origin. | | `ALLOWED_ORIGINS` | server | Comma-separated CORS origins. Empty allows any origin. |
| `HUB_PATH` | server | Hub endpoint path (default `/hub`). | | `HUB_PATH` | server | Hub endpoint path (default `/hub`). |
| `PATH_BASE` | server | Prefix to accept when the proxy forwards a sub-path unstripped, e.g. `/server`. |
| `Game__*` | server | Overrides any `GameSettings` value, e.g. `Game__ItemSpawnChance=0.2`. | | `Game__*` | server | Overrides any `GameSettings` value, e.g. `Game__ItemSpawnChance=0.2`. |
## Extending ## Extending
@@ -10,6 +10,9 @@ var builder = WebApplication.CreateBuilder(args);
var allowedOrigins = (builder.Configuration["ALLOWED_ORIGINS"] ?? "") var allowedOrigins = (builder.Configuration["ALLOWED_ORIGINS"] ?? "")
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var hubPath = builder.Configuration["HUB_PATH"] ?? "/hub"; var hubPath = builder.Configuration["HUB_PATH"] ?? "/hub";
// Optional prefix when the reverse proxy forwards a sub-path without stripping it, e.g. "/server".
// Requests without the prefix keep working.
var pathBase = builder.Configuration["PATH_BASE"] ?? "";
builder.Services.AddCors(options => options.AddDefaultPolicy(policy => builder.Services.AddCors(options => options.AddDefaultPolicy(policy =>
{ {
@@ -35,6 +38,11 @@ builder.Services.AddHostedService<MatchRunner>();
var app = builder.Build(); var app = builder.Build();
if (!string.IsNullOrWhiteSpace(pathBase))
{
app.UsePathBase(pathBase);
}
app.UseCors(); app.UseCors();
app.MapGet("/health", () => Results.Ok(new { status = "ok" })); app.MapGet("/health", () => Results.Ok(new { status = "ok" }));
app.MapHub<GameHub>(hubPath); app.MapHub<GameHub>(hubPath);
+8 -2
View File
@@ -28,8 +28,14 @@ server {
add_header Cache-Control "no-cache"; add_header Cache-Control "no-cache";
} }
# Single page app fallback.
location / { location / {
try_files $uri $uri/ /index.html; try_files $uri $uri/ @prefixed;
}
# The reverse proxy may forward a sub-path without stripping it (e.g. /website/static/x.js).
# Drop the leading path segment and try again; fall back to the app's index.html.
location @prefixed {
rewrite ^/[^/]+(/.*)$ $1 last;
rewrite ^ /index.html last;
} }
} }
+6 -2
View File
@@ -2,14 +2,18 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<!-- Relative asset URLs need a trailing slash when served under a sub-path (/website -> /website/). -->
<script>
if (!/\/$|\.[a-z0-9]+$/i.test(location.pathname)) location.replace(location.pathname + '/' + location.search + location.hash);
</script>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="./assets/favicon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet" />
<title>Action Blocks</title> <title>Action Blocks</title>
<!-- Runtime configuration, rewritten by the container on startup. --> <!-- Runtime configuration, rewritten by the container on startup. -->
<script src="/config.js"></script> <script src="./config.js"></script>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+10 -7
View File
@@ -3,6 +3,9 @@
* (or point these paths elsewhere) to change the look without touching components. * (or point these paths elsewhere) to change the look without touching components.
*/ */
/** Resolves a file in /public relative to where the site is served (works under sub-paths). */
const asset = (path: string) => `${import.meta.env.BASE_URL}${path}`;
export interface ItemVisual { export interface ItemVisual {
/** Icon drawn on board cells, in inventories and item bars. */ /** Icon drawn on board cells, in inventories and item bars. */
icon: string; icon: string;
@@ -17,31 +20,31 @@ export interface ItemVisual {
/** Keyed by the server item id (IItem.Id). Add an entry here when adding an item on the server. */ /** Keyed by the server item id (IItem.Id). Add an entry here when adding an item on the server. */
export const itemVisuals: Record<string, ItemVisual> = { export const itemVisuals: Record<string, ItemVisual> = {
add_line: { add_line: {
icon: '/assets/items/add_line.svg', icon: asset('assets/items/add_line.svg'),
name: 'Add Line', name: 'Add Line',
color: '#ff4256', color: '#ff4256',
description: 'Pushes a garbage line into the target field.', description: 'Pushes a garbage line into the target field.',
}, },
clear_line: { clear_line: {
icon: '/assets/items/clear_line.svg', icon: asset('assets/items/clear_line.svg'),
name: 'Clear Line', name: 'Clear Line',
color: '#4fe35f', color: '#4fe35f',
description: 'Removes the bottom line of the target.', description: 'Removes the bottom line of the target.',
}, },
nuke: { nuke: {
icon: '/assets/items/nuke.svg', icon: asset('assets/items/nuke.svg'),
name: 'Wipe Out', name: 'Wipe Out',
color: '#ffe23f', color: '#ffe23f',
description: 'Clears every placed block of the target.', description: 'Clears every placed block of the target.',
}, },
swap: { swap: {
icon: '/assets/items/swap.svg', icon: asset('assets/items/swap.svg'),
name: 'Switch', name: 'Switch',
color: '#3fe3f2', color: '#3fe3f2',
description: 'Trades your whole field with the target.', description: 'Trades your whole field with the target.',
}, },
blind: { blind: {
icon: '/assets/items/blind.svg', icon: asset('assets/items/blind.svg'),
name: 'Blackout', name: 'Blackout',
color: '#c452ff', color: '#c452ff',
description: 'Hides placed blocks and the drop shadow for 4 seconds.', description: 'Hides placed blocks and the drop shadow for 4 seconds.',
@@ -49,7 +52,7 @@ export const itemVisuals: Record<string, ItemVisual> = {
}; };
export const unknownItem: ItemVisual = { export const unknownItem: ItemVisual = {
icon: '/assets/items/unknown.svg', icon: asset('assets/items/unknown.svg'),
name: 'Item', name: 'Item',
color: '#ffffff', color: '#ffffff',
description: '', description: '',
@@ -58,7 +61,7 @@ export const unknownItem: ItemVisual = {
export const uiAssets = { export const uiAssets = {
/** Optional logo image. When null the text logo is rendered. */ /** Optional logo image. When null the text logo is rendered. */
logo: null as string | null, logo: null as string | null,
favicon: '/assets/favicon.svg', favicon: asset('assets/favicon.svg'),
}; };
export function itemVisual(id: string): ItemVisual { export function itemVisual(id: string): ItemVisual {
+2
View File
@@ -6,6 +6,8 @@ const devServer = process.env.DEV_SERVER_URL ?? 'http://localhost:5080';
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
// Relative URLs so the site works under any sub-path (e.g. https://example.com/website/).
base: './',
build: { build: {
// Hashed bundles go to /static; /assets holds the exchangeable graphics from public/. // Hashed bundles go to /static; /assets holds the exchangeable graphics from public/.
assetsDir: 'static', assetsDir: 'static',
+6 -2
View File
@@ -6,8 +6,11 @@
# website -> actionblocks-website:80 # website -> actionblocks-website:80
# game hub -> actionblocks-server:8080, path /hub (needs WebSocket upgrade support: # game hub -> actionblocks-server:8080, path /hub (needs WebSocket upgrade support:
# "Upgrade" and "Connection" headers must be forwarded). # "Upgrade" and "Connection" headers must be forwarded).
# If the hub is served from a different host, set SERVER_URL accordingly and list the # Sub-paths are supported (e.g. site at https://example.com/website/, server at /server):
# website origin in ALLOWED_ORIGINS. # - the website works whether or not the proxy strips its prefix;
# - set SERVER_URL to the hub URL the browser should use, e.g. https://example.com/server/hub;
# - if the proxy does NOT strip the server prefix, set PATH_BASE=/server.
# If the hub is served from a different host, also list the website origin in ALLOWED_ORIGINS.
services: services:
actionblocks-website: actionblocks-website:
@@ -26,6 +29,7 @@ services:
environment: environment:
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-} ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-}
HUB_PATH: /hub HUB_PATH: /hub
PATH_BASE: ${PATH_BASE:-}
networks: networks:
- actionBlocks_server - actionBlocks_server