Rusty Kingdom est un jeu de gestion avec quelques subtilités :
- il a été conçu pour être un jeu incrémental mais pourrait évoluer vers d'autres genres de jeux.
- Tous les composants de base sont écrits en Rust afin de pouvoir supporter une très forte charge.
- Les joueurs sont incités à écrire leur propre client et leurs propres bots pour interagir avec le serveur de jeu.
- Le client par défaut est une CLI, cependant le jeu doit pouvoir être compatible avec n'importe quel type de client, notamment :
- Script (bash + curl)
- Binaire (Rust + Reqwest)
- Webapp (HTML + CSS + JS)
- gRPC, pourquoi? la promesse de gRPC est de pouvoir ecrire une api 5 a 10 fois plus rapide qu'une api REST classique si c'est vrai, cela voudrat dire que le seul point limitant restant sera postgrsql il reste envisageable d'ajouter un proxy pour permettre a un client d'utiliser une api rest classique.
wget https://github.com/alisterd51/rusty-kingdom/releases/download/nightly/game-client
chmod +x ./game-client
source <(./game-client completions bash)
./game-client --version# Create your fortress
./game-client fortress new | jq
# See other commands
./game-client --helpVous êtes encouragé à créer votre propre serveur privé. Voici quelques possibilités pour y parvenir :
Cet exemple montre comment est déployé le serveur officiel (accessible via https://rusty.anclarma.fr).
flowchart TD
%% =======================
%% CLIENT LAYER
%% =======================
subgraph CLIENT["Client Side"]
B[Browser]
C[CLI]
end
%% =======================
%% SERVER LAYER
%% =======================
subgraph SERVER["Server Side"]
subgraph EDGE["Edge / Gateway"]
T[Traefik]
end
subgraph PUBLIC["Public Services"]
GF[game_frontend]
GS[game_server]
AUTH[rauthy]
end
subgraph PRIVATE["Private Services"]
CRUD[crud_server]
MIG[migration]
PG[(Postgres)]
RINIT[rauthy-init]
end
end
AUTH ~~~ RINIT
%% =======================
%% CLIENT → EDGE
%% =======================
CLIENT -->|https://rusty.anclarma.fr| T
CLIENT -->|https://auth.rusty.anclarma.fr| T
%% =======================
%% EDGE → SERVICES
%% =======================
T -->|frontend| GF
T -->|gRPC-Web / Browser| GS
T -->|gRPC / CLI| GS
T -->|auth| AUTH
%% =======================
%% INTERNAL SERVICE FLOW
%% =======================
GS -->|gRPC| CRUD
CRUD -->|SQL| PG
MIG -->|init DB| PG
RINIT -->|Init Rauthy| AUTH
(Le fichier .env est dérivé de sample.env.)
(les hardened-images nécessitent un docker login dhi.io)
compose.yaml
services:
postgres:
image: dhi.io/postgres:18-alpine3.22
restart: always
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
networks:
- rusty-network
healthcheck:
test: ["CMD-SHELL", "sh -c 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'"]
interval: 5s
timeout: 3s
retries: 10
migration:
image: ghcr.io/alisterd51/rusty-migration:latest
environment:
DATABASE_URL: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres/${POSTGRES_DB}"
networks:
- rusty-network
depends_on:
postgres:
condition: service_healthy
crud_server:
image: ghcr.io/alisterd51/rusty-crud-server:latest
restart: always
environment:
DATABASE_URL: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres/${POSTGRES_DB}"
networks:
- rusty-network
depends_on:
postgres:
condition: service_healthy
migration:
condition: service_completed_successfully
game_server:
image: ghcr.io/alisterd51/rusty-game-server:latest
restart: always
environment:
CRUD_SERVER_URL: "http://crud_server:3000"
AUTH_URL: "http://rauthy:8080"
ISSUER_URL: "https://auth.rusty.anclarma.fr"
networks:
- rusty-network
- traefik-network
game_frontend:
image: ghcr.io/alisterd51/rusty-game-frontend:latest
restart: always
networks:
- traefik-network
init-acme:
image: dhi.io/busybox:1.37-dev
user: root
volumes:
- acme:/acme:rw
command: chown -R 65532:65532 /acme
traefik:
depends_on:
init-acme:
condition: service_completed_successfully
image: dhi.io/traefik:3.6
restart: always
ports:
- "80:80"
- "443:443/tcp"
- "443:443/udp"
volumes:
- ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- ./traefik/config/dynamic:/config/dynamic:ro
- acme:/acme:rw
networks:
- traefik-network
rauthy:
image: ghcr.io/sebadob/rauthy:0.35.1
restart: always
environment:
ENC_KEYS: ${ENC_KEYS}
ENC_KEY_ACTIVE: ${ENC_KEY_ACTIVE}
HQL_SECRET_RAFT: ${HQL_SECRET_RAFT}
HQL_SECRET_API: ${HQL_SECRET_API}
BOOTSTRAP_API_KEY: ${BOOTSTRAP_API_KEY}
BOOTSTRAP_API_KEY_SECRET: ${BOOTSTRAP_API_KEY_SECRET}
volumes:
- ./rauthy/config.toml:/app/config.toml:ro
- rauthy-data:/app/data:rw
networks:
- rauthy-network
- traefik-network
rauthy-init:
image: ghcr.io/alisterd51/rusty-rauthy-init:latest
restart: on-failure
environment:
RAUTHY_URL: "http://rauthy:8080"
BOOTSTRAP_API_KEY_NAME: "bootstrap"
BOOTSTRAP_API_KEY_SECRET: ${BOOTSTRAP_API_KEY_SECRET}
networks:
- rauthy-network
volumes:
acme:
rauthy-data:
networks:
rusty-network:
external: false
rauthy-network:
external: false
traefik-network:
external: falsetraefik/traefik.yml
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: ":443"
http3: {}
certificatesResolvers:
myresolver:
acme:
email: antoinereims28@gmail.com
storage: /acme/acme.json
httpChallenge:
entryPoint: web
providers:
file:
directory: "/config/dynamic"
watch: truetraefik/config/dynamic/routes.yml
http:
routers:
game-frontend:
rule: "Host(`rusty.anclarma.fr`)"
service: game-frontend-service
middlewares:
- compress
entryPoints:
- websecure
tls:
certresolver: myresolver
game-server:
rule: "Host(`rusty.anclarma.fr`) && PathPrefix(`/game.`)"
service: game-server-service
entryPoints:
- websecure
tls:
certresolver: myresolver
rauthy:
rule: "Host(`auth.rusty.anclarma.fr`)"
service: rauthy-service
entryPoints:
- websecure
tls:
certresolver: myresolver
middlewares:
compress:
compress: {}
services:
game-frontend-service:
loadBalancer:
servers:
- url: "http://game_frontend:80"
game-server-service:
loadBalancer:
servers:
- url: "h2c://game_server:3000"
rauthy-service:
loadBalancer:
servers:
- url: "http://rauthy:8080"rauthy/config.toml
[encryption]
[cluster]
node_id = 1
[server]
scheme = 'http'
pub_url = 'auth.rusty.anclarma.fr'
proxy_mode = true
trusted_proxies = [
'172.16.0.0/12',
]
[webauthn]
rp_id = 'auth.rusty.anclarma.fr'
rp_origin = 'https://auth.rusty.anclarma.fr:443'
[mfa]
admin_force_mfa = falseTODO