Configuration

View Source

Asobi supports two configuration paths depending on how you use it.

Lua (Docker)

For Lua game developers using the Docker image, configuration lives in your Lua scripts. No Erlang syntax needed.

Game Mode Config

Declare settings as globals at the top of your match script:

-- match.lua
match_size = 4
max_players = 10
strategy = "fill"
bots = { script = "bots/arena_bot.lua" }
GlobalRequiredDefaultDescription
match_sizeyes--Minimum players to start a match
max_playersnomatch_sizeMaximum players per match
strategyno"fill""fill", "skill_based", or custom
botsnonone{ script = "path/to/bot.lua" }

Multiple Game Modes

Add a config.lua manifest mapping mode names to scripts:

-- config.lua
return {
    arena = "arena/match.lua",
    ctf   = "ctf/match.lua"
}

Infrastructure Config

Infrastructure settings come from environment variables:

VariableDefaultDescription
ASOBI_PORT8080HTTP/WebSocket port
ASOBI_DB_HOSTdbPostgreSQL host
ASOBI_DB_NAMEasobiDatabase name
ASOBI_DB_USERpostgresDatabase user
ASOBI_DB_PASSWORDpostgresDatabase password
ASOBI_DB_SOCKET_OPTSinet (set by asobi_lua image; empty when consuming asobi directly)Erlang term fragment spliced into the kura socket_options list. Examples: inet, inet6, inet, {nodelay, true}. Set to inet6 for IPv6-only Postgres networks.
ASOBI_CORS_ORIGINS*Allowed CORS origins
ASOBI_NODE_HOST127.0.0.1Erlang node hostname
ERLANG_COOKIEasobi_cookieErlang distribution cookie

Erlang (sys.config)

For Erlang OTP projects that add asobi as a dependency, all configuration lives in sys.config under the {asobi, [...]} key.

Game Modes

{game_modes, #{
    ~"arena" => #{
        module => my_arena_game,
        match_size => 4,
        max_players => 8,
        strategy => fill
    }
}}

Lua scripts work too:

{game_modes, #{
    ~"arena" => #{
        module => {lua, "game/match.lua"},
        match_size => 4,
        max_players => 8,
        strategy => fill
    }
}}

Shorthand (Erlang module only):

{game_modes, #{
    ~"arena" => my_arena_game
}}

Mode Options

OptionDefaultDescription
modulerequiredErlang module or {lua, "path.lua"}
match_size2Players needed to start a match
max_players10Maximum players per match
strategyfillMatchmaking strategy: fill, skill_based, or custom module
skill_window200Initial skill difference allowed (skill_based only)
skill_expand_rate50Window expansion per 5 seconds (skill_based only)
bots#{}Bot configuration (see Bots)

Matchmaker

{matchmaker, #{
    tick_interval => 1000,     %% ms between matchmaker ticks (default 1000)
    max_wait_seconds => 60     %% ticket expiry (default 60)
}}

Sessions

Session token lifetime is handled by Nova's nova_auth_session — configure there (not under asobi). See the Nova docs for token/refresh TTL settings.

Rate Limiting

Per-route-group rate limits using sliding window algorithm via Seki.

{rate_limits, #{
    auth => #{limit => 10, window => 60000},    %% 10 req/min for auth
    iap  => #{limit => 300, window => 1000},    %% 300 req/sec for IAP
    api  => #{limit => 300, window => 1000}     %% 300 req/sec for API
}}

Default: 300 requests per second for all groups.

CORS

CORS is handled by nova_cors_plugin in the Nova plugin chain — configure it under {nova, [{plugins, [...]}]}:

{nova, [
    {plugins, [
        {pre_request, nova_cors_plugin, #{allow_origins => ~"https://mygame.com"}}
    ]}
]}

Clustering

Optional multi-node clustering via Erlang distribution.

{cluster, #{
    strategy => dns,
    dns_name => "my-game.internal",
    poll_interval => 10000
}}

EPMD Strategy (for static hosts)

{cluster, #{
    strategy => epmd,
    hosts => ['node1@host1', 'node2@host2']
}}

Authentication Providers

OAuth/OIDC

{oidc_providers, #{
    google => #{
        client_id => ~"...",
        client_secret => ~"...",
        discovery_url => ~"https://accounts.google.com/.well-known/openid-configuration"
    },
    discord => #{
        client_id => ~"...",
        client_secret => ~"...",
        authorize_url => ~"https://discord.com/api/oauth2/authorize",
        token_url => ~"https://discord.com/api/oauth2/token",
        userinfo_url => ~"https://discord.com/api/users/@me"
    }
}}

Steam

{steam_api_key, ~"your-steam-web-api-key"},
{steam_app_id, ~"480"}

Apple/Google IAP

{apple_bundle_id, ~"com.example.mygame"},
{google_package_name, ~"com.example.mygame"},
{google_service_account_key, ~"/path/to/service-account.json"}

Vote Templates

Define reusable vote configurations:

{vote_templates, #{
    ~"map_vote" => #{
        method => ~"plurality",
        window_ms => 15000,
        visibility => ~"live"
    },
    ~"boon_pick" => #{
        method => ~"plurality",
        window_ms => 15000,
        visibility => ~"live"
    }
}}

Templates are merged with per-vote config from your game module.

Database (Kura)

Database configuration is under the kura application key:

{kura, [
    {repo, asobi_repo},
    {host, "localhost"},
    {port, 5432},
    {database, "my_game_dev"},
    {user, "postgres"},
    {password, "postgres"},
    {pool_size, 10}
]}

Background Jobs (Shigoto)

{shigoto, [
    {pool, asobi_repo}
]}

Full Example (Erlang sys.config)

[
    {kura, [
        {repo, asobi_repo},
        {host, "localhost"},
        {database, "my_game_dev"},
        {user, "postgres"},
        {password, "postgres"},
        {pool_size, 20}
    ]},
    {shigoto, [
        {pool, asobi_repo}
    ]},
    {asobi, [
        {rate_limits, #{
            auth => #{limit => 10, window => 60000},
            api => #{limit => 300, window => 1000}
        }},
        {matchmaker, #{
            tick_interval => 1000,
            max_wait_seconds => 60
        }},
        {game_modes, #{
            ~"arena" => #{
                module => {lua, "game/match.lua"},
                match_size => 4,
                max_players => 8,
                strategy => fill,
                bots => #{
                    enabled => true,
                    fill_after_ms => 8000,
                    min_players => 4,
                    script => <<"game/bots/chaser.lua">>
                }
            }
        }}
    ]}
].

Full Example (Lua Docker)

# docker-compose.yml
services:
  postgres:
    image: postgres:17
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: my_game_dev
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  asobi:
    image: ghcr.io/widgrensit/asobi_lua:latest
    depends_on:
      postgres: { condition: service_healthy }
    ports:
      - "8080:8080"
    volumes:
      - ./lua:/app/game:ro
    environment:
      ASOBI_DB_HOST: postgres
      ASOBI_DB_NAME: my_game_dev
-- lua/match.lua
match_size = 4
max_players = 8
strategy = "fill"
bots = { script = "bots/chaser.lua" }

function init(config)
    return { players = {} }
end

-- ... rest of callbacks
-- lua/bots/chaser.lua
names = {"Spark", "Blitz", "Volt", "Neon"}

function think(bot_id, state)
    -- AI logic
end