Enhance API with OIDC support, including login and callback endpoints. Update environment variables for OIDC configuration in .env.example. Add new features to the catalog service for listing software families, Minecraft versions, and deployment regions. Implement server management actions such as kill, delete, and update in the servers module. Integrate feature flags for maintenance mode in server operations. Update pnpm-lock.yaml with new dependencies and versions.
Some checks failed
CI / Node — lint, typecheck, test, build (push) Failing after 12s
CI / Go — node-agent tests (push) Failing after 9s
CI / Go — edge-gateway build (push) Successful in 17s

This commit is contained in:
TheOnlyMace
2026-07-05 18:39:53 +02:00
parent bf36cb3159
commit 50cd4b3ffd
225 changed files with 17824 additions and 436 deletions

View File

@@ -0,0 +1,242 @@
# HexaHost GameCloud — production Docker Compose stack
# Usage:
# docker network create traefik-network
# cp .env.example .env.prod && edit secrets/domains
# docker compose -f deploy/compose/compose.prod.yml --env-file .env.prod up -d
name: hexahost-gamecloud-prod
services:
postgres:
image: postgres:16-alpine
restart: always
env_file:
- ../../.env.prod
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB:-gamecloud}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB:-gamecloud}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- gamecloud-internal
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
redis:
image: redis:7-alpine
restart: always
command: ["redis-server", "--appendonly", "yes", "--appendfsync", "everysec"]
env_file:
- ../../.env.prod
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- gamecloud-internal
logging:
driver: json-file
options:
max-size: "20m"
max-file: "3"
minio:
image: minio/minio:RELEASE.2024-12-18T13-15-44Z
restart: always
command: server /data --console-address ":9001"
env_file:
- ../../.env.prod
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
volumes:
- minio_data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 15s
timeout: 5s
retries: 5
start_period: 30s
networks:
- gamecloud-internal
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
minio-init:
image: minio/mc:RELEASE.2024-11-05T11-14-20Z
restart: "no"
depends_on:
minio:
condition: service_healthy
env_file:
- ../../.env.prod
entrypoint: >
/bin/sh -c "
mc alias set local http://minio:9000 $${S3_ACCESS_KEY} $${S3_SECRET_KEY};
mc mb --ignore-existing local/$${S3_BUCKET:-gamecloud};
mc anonymous set none local/$${S3_BUCKET:-gamecloud};
exit 0;
"
environment:
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_BUCKET: ${S3_BUCKET:-gamecloud}
networks:
- gamecloud-internal
api:
build:
context: ../..
dockerfile: apps/api/Dockerfile
image: hexahost-gamecloud-api:${GAMECLOUD_IMAGE_TAG:-latest}
restart: always
env_file:
- ../../.env.prod
environment:
NODE_ENV: production
APP_ENV: production
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-gamecloud}
REDIS_URL: redis://redis:6379
S3_ENDPOINT: http://minio:9000
TRUSTED_PROXY_COUNT: ${TRUSTED_PROXY_COUNT:-1}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
minio:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3001/api/v1/health/live"]
interval: 15s
timeout: 5s
retries: 5
start_period: 60s
networks:
- gamecloud-internal
- traefik-network
labels:
- traefik.enable=true
- traefik.docker.network=${TRAEFIK_NETWORK:-traefik-network}
- traefik.http.routers.gamecloud-api.rule=Host(`${API_HOST:-api.example.net}`)
- traefik.http.routers.gamecloud-api.entrypoints=websecure
- traefik.http.routers.gamecloud-api.tls=true
- traefik.http.routers.gamecloud-api.tls.certresolver=letsencrypt
- traefik.http.routers.gamecloud-api.middlewares=gamecloud-api-headers@file,gamecloud-rate-limit@file
- traefik.http.routers.gamecloud-api.service=gamecloud-api
- traefik.http.services.gamecloud-api.loadbalancer.server.port=3001
- traefik.http.routers.gamecloud-api-http.rule=Host(`${API_HOST:-api.example.net}`)
- traefik.http.routers.gamecloud-api-http.entrypoints=web
- traefik.http.routers.gamecloud-api-http.middlewares=gamecloud-redirect-https@file
logging:
driver: json-file
options:
max-size: "100m"
max-file: "10"
worker:
build:
context: ../..
dockerfile: apps/worker/Dockerfile
image: hexahost-gamecloud-worker:${GAMECLOUD_IMAGE_TAG:-latest}
restart: always
env_file:
- ../../.env.prod
environment:
NODE_ENV: production
APP_ENV: production
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-gamecloud}
REDIS_URL: redis://redis:6379
S3_ENDPOINT: http://minio:9000
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3002/health"]
interval: 20s
timeout: 5s
retries: 5
start_period: 60s
networks:
- gamecloud-internal
logging:
driver: json-file
options:
max-size: "100m"
max-file: "10"
web:
build:
context: ../..
dockerfile: apps/web/Dockerfile
image: hexahost-gamecloud-web:${GAMECLOUD_IMAGE_TAG:-latest}
restart: always
env_file:
- ../../.env.prod
environment:
NODE_ENV: production
API_URL: http://api:3001
depends_on:
api:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/de"]
interval: 15s
timeout: 5s
retries: 5
start_period: 60s
networks:
- gamecloud-internal
- traefik-network
labels:
- traefik.enable=true
- traefik.docker.network=${TRAEFIK_NETWORK:-traefik-network}
- traefik.http.routers.gamecloud-web.rule=Host(`${WEB_HOST:-panel.example.net}`)
- traefik.http.routers.gamecloud-web.entrypoints=websecure
- traefik.http.routers.gamecloud-web.tls=true
- traefik.http.routers.gamecloud-web.tls.certresolver=letsencrypt
- traefik.http.routers.gamecloud-web.middlewares=gamecloud-web-headers@file
- traefik.http.routers.gamecloud-web.service=gamecloud-web
- traefik.http.services.gamecloud-web.loadbalancer.server.port=3000
- traefik.http.routers.gamecloud-web-http.rule=Host(`${WEB_HOST:-panel.example.net}`)
- traefik.http.routers.gamecloud-web-http.entrypoints=web
- traefik.http.routers.gamecloud-web-http.middlewares=gamecloud-redirect-https@file
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
volumes:
postgres_data:
name: hexahost-gamecloud-postgres
redis_data:
name: hexahost-gamecloud-redis
minio_data:
name: hexahost-gamecloud-minio
networks:
gamecloud-internal:
name: hexahost-gamecloud-internal
driver: bridge
traefik-network:
external: true
name: ${TRAEFIK_NETWORK:-traefik-network}

View File

@@ -0,0 +1,38 @@
# Grafana provisioning — HexaHost GameCloud
# Mount to /etc/grafana/provisioning/datasources/datasources.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false
jsonData:
timeInterval: 15s
httpMethod: POST
- name: Loki
type: loki
access: proxy
url: http://loki:3100
editable: false
jsonData:
maxLines: 1000
derivedFields:
- datasourceUid: prometheus
matcherRegex: "trace_id=(\\w+)"
name: TraceID
url: "$${__value.raw}"
- name: Prometheus (external)
type: prometheus
access: proxy
url: http://localhost:9090
isDefault: false
editable: true
jsonData:
timeInterval: 15s
# Optional — use when Grafana runs outside the compose network

View File

@@ -0,0 +1,56 @@
# Loki configuration stub — HexaHost GameCloud
# Suitable for single-node control plane deployments. Scale to object storage for HA.
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
log_level: info
common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
storage_config:
tsdb_shipper:
active_index_directory: /loki/tsdb-index
cache_location: /loki/tsdb-cache
filesystem:
directory: /loki/chunks
limits_config:
retention_period: 720h
ingestion_rate_mb: 16
ingestion_burst_size_mb: 32
max_streams_per_user: 10000
reject_old_samples: true
reject_old_samples_max_age: 168h
compactor:
working_directory: /loki/compactor
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
delete_request_store: filesystem
analytics:
reporting_enabled: false

View File

@@ -0,0 +1,95 @@
# HexaHost GameCloud — Traefik dynamic configuration
# Shared middleware and optional file-based routers for the control plane stack.
# Primary routing is defined via Docker labels in deploy/compose/compose.prod.yml.
http:
middlewares:
gamecloud-redirect-https:
redirectScheme:
scheme: https
permanent: true
gamecloud-api-headers:
headers:
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
contentTypeNosniff: true
frameDeny: true
referrerPolicy: strict-origin-when-cross-origin
customRequestHeaders:
X-Forwarded-Proto: https
gamecloud-web-headers:
headers:
stsSeconds: 31536000
stsIncludeSubdomains: true
contentTypeNosniff: true
frameDeny: true
referrerPolicy: strict-origin-when-cross-origin
customRequestHeaders:
X-Forwarded-Proto: https
gamecloud-rate-limit:
rateLimit:
average: 100
burst: 200
period: 1s
routers:
# File-provider routers — useful when Traefik runs outside the compose project
# or when you prefer centralised routing. Disable duplicate Docker labels if you
# rely exclusively on these definitions.
gamecloud-api:
rule: Host(`api.example.net`)
entryPoints:
- websecure
service: gamecloud-api
middlewares:
- gamecloud-api-headers
- gamecloud-rate-limit
tls:
certResolver: letsencrypt
gamecloud-web:
rule: Host(`panel.example.net`)
entryPoints:
- websecure
service: gamecloud-web
middlewares:
- gamecloud-web-headers
tls:
certResolver: letsencrypt
services:
gamecloud-api:
loadBalancer:
servers:
- url: http://api:3001
passHostHeader: true
healthCheck:
path: /api/v1/health/live
interval: 15s
timeout: 5s
gamecloud-web:
loadBalancer:
servers:
- url: http://web:3000
passHostHeader: true
healthCheck:
path: /de
interval: 15s
timeout: 5s
tls:
options:
default:
minVersion: VersionTLS12
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305

View File

@@ -0,0 +1,59 @@
# HexaHost GameCloud — Traefik static configuration
# Mount this file read-only when running the Traefik container on the control plane host.
global:
checkNewVersion: false
sendAnonymousUsage: false
api:
dashboard: true
insecure: false
log:
level: INFO
format: json
accessLog:
format: json
filters:
statusCodes:
- "400-599"
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: ":443"
http:
tls:
certResolver: letsencrypt
domains:
- main: "${WEB_HOST:-panel.example.net}"
- main: "${API_HOST:-api.example.net}"
providers:
docker:
endpoint: unix:///var/run/docker.sock
exposedByDefault: false
network: traefik-network
watch: true
file:
directory: /etc/traefik/dynamic
watch: true
certificatesResolvers:
letsencrypt:
acme:
email: "${ACME_EMAIL:-ops@example.net}"
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
serversTransport:
insecureSkipVerify: false