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.
This commit is contained in:
242
deploy/compose/compose.prod.yml
Normal file
242
deploy/compose/compose.prod.yml
Normal 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}
|
||||
38
deploy/monitoring/grafana/datasources.yml
Normal file
38
deploy/monitoring/grafana/datasources.yml
Normal 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
|
||||
56
deploy/monitoring/loki/loki-config.yml
Normal file
56
deploy/monitoring/loki/loki-config.yml
Normal 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
|
||||
95
deploy/traefik/dynamic/gamecloud.yml
Normal file
95
deploy/traefik/dynamic/gamecloud.yml
Normal 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
|
||||
59
deploy/traefik/traefik.yml
Normal file
59
deploy/traefik/traefik.yml
Normal 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
|
||||
Reference in New Issue
Block a user