Skip to content

alisterd51/rusty-kingdom

Repository files navigation

Rusty Kingdom

Nightly

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.

WEB client

https://rusty.anclarma.fr

CLI client

Install

wget https://github.com/alisterd51/rusty-kingdom/releases/download/nightly/game-client
chmod +x ./game-client
source <(./game-client completions bash)
./game-client --version

Start

# Create your fortress
./game-client fortress new | jq

# See other commands
./game-client --help

Serveur privé

Vous êtes encouragé à créer votre propre serveur privé. Voici quelques possibilités pour y parvenir :

Exemple avec Docker Compose et Traefik

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
Loading

(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: false

traefik/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: true

traefik/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 = false

Exemple avec Kubernetes

TODO

About

Rusty Kingdom – Build a kingdom, but beware of the "rust" that could symbolize unexpected challenges.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Languages