diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d8dc07f5f..829330706 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,7 +20,7 @@ jobs: mysql password: 'araknemu' mysql root password: 'root' - - name: Reset mysql password + - name: Create MySQL user run: | while : do @@ -29,9 +29,8 @@ jobs: done cat < -p < resources/db/000-init_mysql.sql +``` + +Now you can run the server (environment variables are listed below): +``` +docker run -p 5555:5555 -p 4444:4444 -e GAME_HOST=172.17.0.1 -e DB_HOST=172.17.0.1 -e DB_PASSWORD=araknemu -v './logs:/srv/logs' araknemu +``` + +Environment variables: + +| Variable | Description | Default value | +|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------| +| `AUTH_PORT` | The port used by the login server. This port should be exposed using `-p` option. | `4444` | +| `CLIENT_VERSION` | The required client version. | `1.29.1` | +| `PASSWORD_HASH` | List of available password hash algorithms, separated by a coma. The first of the list will be used for re-hash the password, so make sure to define the best algorithm at first position. Available values : `argon2`, `plain`. | `argon2, plain` | +| `DB_TYPE` | Database driver to use. Available types : `mysql`, `sqlite`. | `mysql` | +| `DB_HOST` | Database host name (only mysql). You can set a custom port by suffixing it by `:[port]`. If you want to use your locale database, you can set it to `172.17.0.1`. | `127.0.0.1` | +| `DB_USER` | Database username (only mysql). | `araknemu` | +| `DB_PASSWORD` | Database password (only mysql). | empty | +| `DB_NAME` | Database name in case of mysql, of the file path in case of sqlite. Don't forget to declare a volume if you use sqlite driver (e.g. `-v araknemu.db:/srv/araknemu.db`). | mysql: `araknemu` sqlite: `araknemu.db` | +| `DB_POOL_SIZE` | Number of opened connections. Increase this value if the message "Pool is empty" occurs too often on logs. | `8` | +| `GAME_PORT` | The port used by the game server. This port should be exposed using `-p` option. | `5555` | +| `GAME_HOST` | The public host name of the game server. This host must be accessible from internet if you want to open your server to users. | `127.0.0.1` | +| `TIMEZONE` | Timezone used for in game clock and database times. See: https://docs.oracle.com/middleware/12211/wcs/tag-ref/MISC/TimeZones.html | `Europe/Paris` | +| `GAME_SERVER_ID` | The server id to launch. The server must be present in langs, and should be available for the target community. Data are not shared between 2 servers, so if you change the server ID, data will not be present. | `1` (Jiva) | +| `ACTIVITY_THREADS` | Number of threads to launch for run "activities" (e.g. moving monsters, respawn...). Increase this value only if the respawn is to slow. | `1` | +| `RESPAWN_SPEED_FACTOR` | Divide the respawn time of monster groups defined in database. For example, if the monster group is defined to respawn after 1 minute, with a factor of 2, it will respawn after 30 seconds. | `1` | +| `FIGHT_THREADS` | Number of threads used to execute fight actions and AI. The minimal value should be 2. A good value may be around 1 thread per 100 active fights. | `4` | +| `RATE_XP` | The experience gain multiplier. The value should be a positive decimal number. | `1.0` | +| `RATE_DROP` | The object drop multiplier. The value should be a positive decimal number. This value will modify the object drop chance, but not the maximum dropped items per monster nor minimal discernment requirement. | `1.0` | +| `AI_SCRIPT_PATH` | The path where AI scripts are stored. This path must be relative to the docker image. If you want to define custom scripts, don't forget to declare a volume (e.g. `-v ./scripts:/srv/scripts`). If the path doesn't exists yet, it will be ignored. | `scripts/ai` | +| `PRELOAD` | Enable data preloading or not. Values `1`, `true`, `yes`, `on` will enable preloading. Any other values will disable it. Disable preloading allows faster startup, and lower memory usage, but adds some performance penalty. It's advisable to enable it on production with long uptime, and disable it if the server is restarted regularly. | `true` | +| `ADMIN_COMMAND_SCRIPT_PATH` | Base path for admin command scripts. The following sub-directories should be used to declare commands on the respective scope : `account`, `player`, `debug`, `server`. Don't forget to declare a volume (e.g. `-v ./scripts:/srv/scripts`). If the path doesn't exists yet, it will be ignored. | `scripts/commands` | +| `JAVA_MEMORY` | Define the heap memory used by java (same amount is used for start and max memory). If you set to empty value, the memory will be allocated dynamically. This value is a number followed by a letter to define the unit (i.e. `M` for megabyte, `G` for gigabyte). High value will reduce the GC usage and improve performance, but GC pause time can be higher. Note: this is the heap size, not the actual allocated memory by java. The actual memory will be around 1.5 to 2 times the value of the heap size (for example, `512m` will takes around 800 MB of memory). | `512m` | +| `JAVA_GC` | Use a custom garbage collector algorithm. Available values (not exhaustive) `G1GC`, `ZGC`. `G1GC` seems to works well, even with low heap size (most of the pause time is less than 10ms), but non-predictable pause time may cause random lags. `ZGC` will minimize pause time (most are less than 1ms), but use more memory, and quickly trigger out of memory with low heap size. | `G1GC` | +| `JAVA_GC_LOG` | If set to `1`, will enable GC logs into `/srv/logs/gc-*.log`. Don't forget to declare a volume on logs (e.g. `-v ./logs:/srv/logs`). | none (disabled) | +| `JAVA_OPTS` | Custom java options. | none | + +Advanced configuration: + +If provided environment variables are not enough, you can create a custom `config.ini` file, and mount it into the container. +The file should be placed in the `/srv/config.ini` path. + +- Copy the default configuration file + ``` + cp config.ini.dist config.ini + ``` +- Edit the configuration file +- Run the container with the volume option + ``` + docker run -v './config.ini:/srv/config.ini' [...] araknemu + ``` + +### Docker-compose + +You can use the provided `docker-compose.yml` file to run the server in a container. + +- Create a `.env` file with desired configuration (See [Docker section](#docker) for environment variables) + ``` + # Use the mariadb server from the db service + DB_HOST=db + + # You can define any other environment variables here + GAME_HOST=myserver.example.com + JAVA_MEMORY=1G + ``` +- Configure the `docker-compose.yml` file if needed (e.g. change the ports, add volumes, etc.) +- Build the server + ``` + docker-compose build + ``` +- Setup the database by copying the SQL file containing the dataset into `resources/db/`. The database structure is already created by the file `000-init_mysql.sql`. +- Run the server (can take some time to start the first time because of the database setup) + ``` + docker-compose up + ``` + +### Local + +#### Prerequisites - Java JDK >= 8 - Maven - Git - MySQL (or MariaDB) -### Installing +#### Installing **Note:** Those instruction enable the setup of a development environment, not a production one -- Clone the repository and go to the project directory - ``` - git clone https://github.com/Arakne/Araknemu.git && cd Araknemu - ``` - Build the JAR ``` mvn package -DskipTests=true ``` - Create Mysql database structure (the data are not provided) ``` - mysql -u -p < resources/init_mysql.sql + mysql -u -p < resources/db/000-init_mysql.sql + ``` +- Copy the `config.ini.dist` file ``` -- Configure the `conf.ini` file + cp conf.ini.dist conf.ini + ``` +- Configure the `config.ini` file -### Running +#### Running Run Mysql server. Launch the built jar. -To run the server, your working directory should contains the `conf.ini` file. +To run the server, your working directory should contain the `config.ini` file. ``` java -jar target/araknemu-0.11-alpha-jar-with-dependencies.jar @@ -84,13 +186,27 @@ To see all currently implemented features you can go to [closed feature issues]( #### Where is the database ? This project only provides the server source code. You should create your own database in order to launch the server. -A command will be created to generate the database structure. +You can find the database structure in the file `resources/db/000-init_mysql.sql`. + +To create your own database, you must either convert an existing database, or extract data from dofus lang files and maps. + +> [!NOTE] +> In the future, a command may be created to partially generate the database. #### Why this project ? There is a lot of other dofus emulators... -All dofus servers I've seen have very bad quality standards, don't follow semantic versioning, -are always on rolling release (i.e. no maintained version), and no unit and functional tests. -So, the goal of this project is to provide a reliable open source base for Dofus servers. +Most other servers are designed to be feature-rich, and provide an opinionated version of the Dofus experience. +Instead of that, this project is designed to be modular, and provide a reliable base for developers +to build their own experience. + +The reliability of the server is based on automated tests (CI), continuous stability tests, +and mostly following semantic versioning. + +The key goals of this project are (at least): +- Modularity and extensibility for developers to build their own experience +- At least 1 month of uptime without any instabilities +- No detectable memory leaks +- Handle at least 1000 players connected at the same time with less than 1GB of memory and without noticeable lags ## Contributing @@ -98,4 +214,4 @@ Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of cond ## Licence -This project is licensed under the LGPLv3 licence. See [COPYING](./COPYING) and [COPYING.LESSER](./COPYING.LESSER) files for details. \ No newline at end of file +This project is licensed under the LGPLv3 licence. See [COPYING](./COPYING) and [COPYING.LESSER](./COPYING.LESSER) files for details. diff --git a/config.ini.dist b/config.ini.dist index 0a015f119..37c6c1827 100644 --- a/config.ini.dist +++ b/config.ini.dist @@ -20,7 +20,7 @@ client.version = ${CLIENT_VERSION:-1.29.1} ; > Available algorithms : argon2, plain ; > The first value will be the default hash algorithm ; > Default value: "argon2, plain" -;password.defaultHash = argon2 +password.defaultHash = "${PASSWORD_HASH:-argon2, plain}" ; > The number of iterations. Default: 4 ;password.argon2.iterations = 4 ; > The memory, in kilobits. Default: 65536 (64Mo) @@ -89,6 +89,7 @@ realm.user = ${DB_USER:-araknemu} realm.password = $DB_PASSWORD realm.dbname = ${DB_NAME:-araknemu} realm.path = ${DB_NAME:-araknemu.db} +realm.poolSize = ${DB_POOL_SIZE:-8} ; Configure database for game server game.type = ${DB_TYPE:-mysql} @@ -96,6 +97,7 @@ game.host = ${DB_HOST:-127.0.0.1} game.user = ${DB_USER:-araknemu} game.password = $DB_PASSWORD game.dbname = ${DB_NAME:-araknemu} +game.poolSize = ${DB_POOL_SIZE:-8} [game] ; Section for configure the game server @@ -113,7 +115,7 @@ server.timezone = ${TIMEZONE:-Europe/Paris} ; Antispam & other ; ---------------- ; > The server ID. By default 1 -;id = 1 +id = ${GAME_SERVER_ID:-1} ; > The maximum inactivity time, as duration ; > By default, 15min ("PT15M" or "15m") ;inactivityTime = 15m @@ -184,7 +186,7 @@ server.timezone = ${TIMEZONE:-Europe/Paris} ; Activity ; -------- ; > Number of threads to use for the activity service -;activity.threadsCount = 1 +activity.threadsCount = ${ACTIVITY_THREADS:-1} ; > Number of seconds for move monster groups ; > By default 120s = 2min ;activity.monsters.moveInterval = 120 @@ -198,7 +200,7 @@ server.timezone = ${TIMEZONE:-Europe/Paris} ; > The delay divisor for respawn a monster group ; > With a factor of 2, the respawn will be 2 times faster ; > By default 1 -;activity.monsters.respawnSpeedFactor = 1 +activity.monsters.respawnSpeedFactor = ${RESPAWN_SPEED_FACTOR:-1} ; Economy ; ------- @@ -217,7 +219,7 @@ server.timezone = ${TIMEZONE:-Europe/Paris} ; > The threads count for run fight actions and AI ; > This value should be greater than 2. A good value may be around 1 thread per 100 fights ; > By default, 4 -;fight.threadsCount = 4 +fight.threadsCount = ${FIGHT_THREADS:-4} ; > The fight turn duration ; > The value should be a duration string like 30s, 1m10s ; > Default value : 30s @@ -229,13 +231,13 @@ server.timezone = ${TIMEZONE:-Europe/Paris} ; > Get the XP multiplier ; > The value should be a positive decimal number. ; > Default value : 1.0 -fight.rate.xp = ${XP_RATE:-1.0} +fight.rate.xp = ${RATE_XP:-1.0} ; > Get the drop rate multiplier ; > The value should be a positive decimal number. ; > This value will modify the object drop chance, but not the maximum dropped items per monster ; > nor minimal discernment requirement. ; > Default value : 1.0 -fight.rate.drop = ${DROP_RATE:-1.0} +fight.rate.drop = ${RATE_DROP:-1.0} ; > Get the initial erosion rate for fighters ; > This value is a percentage, representing the percent of damage that will be transformed to permanent life loss ; > It must be between 0 and 100, where 0 means no erosion and 100 means all damage are permanent diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 000000000..1bd4c89d0 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,30 @@ +services: + araknemu: + build: + context: . + dockerfile: Dockerfile + target: production + env_file: + - .env + restart: on-failure # Database may not be ready when the app starts + ports: + - "4444:4444" + - "5555:5555" + volumes: + - ./logs:/srv/logs + - ./scripts:/srv/scripts + #- ./araknemu.db:/srv/araknemu.db + #- ./config.ini:/srv/config.ini + depends_on: + - db + + db: + image: mariadb:11.3 + environment: + MARIADB_RANDOM_ROOT_PASSWORD: 1 + MARIADB_DATABASE: araknemu + MARIADB_USER: araknemu + MARIADB_PASSWORD: araknemu + volumes: + - ./db:/var/lib/mysql + - ./resources/db:/docker-entrypoint-initdb.d diff --git a/resources/init_mysql.sql b/resources/db/000-init_mysql.sql similarity index 100% rename from resources/init_mysql.sql rename to resources/db/000-init_mysql.sql diff --git a/src/main/java/fr/quatrevieux/araknemu/core/dbal/SQLiteDriver.java b/src/main/java/fr/quatrevieux/araknemu/core/dbal/SQLiteDriver.java index 61d0f5a11..6d491adaf 100644 --- a/src/main/java/fr/quatrevieux/araknemu/core/dbal/SQLiteDriver.java +++ b/src/main/java/fr/quatrevieux/araknemu/core/dbal/SQLiteDriver.java @@ -30,6 +30,12 @@ public final class SQLiteDriver implements Driver { private final DatabaseConfiguration.Connection configuration; public SQLiteDriver(DatabaseConfiguration.Connection configuration) { + try { + Class.forName("org.sqlite.JDBC"); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("SQLite JDBC driver not found", e); + } + this.configuration = configuration; } diff --git a/src/test/java/fr/quatrevieux/araknemu/core/dbal/DatabaseConfigurationTest.java b/src/test/java/fr/quatrevieux/araknemu/core/dbal/DatabaseConfigurationTest.java index 571636695..fb70452a2 100644 --- a/src/test/java/fr/quatrevieux/araknemu/core/dbal/DatabaseConfigurationTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/core/dbal/DatabaseConfigurationTest.java @@ -81,7 +81,7 @@ void test_mysql() { assertEquals("mysql", config.type()); assertEquals("araknemu", config.dbname()); assertEquals("araknemu", config.user()); - assertEquals("", config.password()); + assertEquals("araknemu", config.password()); assertEquals(16, config.maxPoolSize()); } diff --git a/src/test/test_config.ini b/src/test/test_config.ini index 66b2cfcd9..a54628999 100644 --- a/src/test/test_config.ini +++ b/src/test/test_config.ini @@ -38,6 +38,7 @@ test_sqlite.path = test.db test_mysql.host = localhost test_mysql.user = araknemu +test_mysql.password = araknemu test_mysql.dbname = araknemu test_ar.type = sqlite diff --git a/start.sh b/start.sh new file mode 100644 index 000000000..84f38bf94 --- /dev/null +++ b/start.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# +# This file is part of Araknemu. +# +# Araknemu is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Araknemu is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Araknemu. If not, see . +# +# Copyright (c) 2017-2024 Vincent Quatrevieux +# + +# ============================================================================== +# This script is used to start Araknemu from a Docker container. +# It relies on environment variables to configure the JVM and wait for MariaDB. +# If you want to run Araknemu outside of a Docker container, use directly +# `java -jar target/araknemu-xxx-alpha-jar-with-dependencies.jar` +# ============================================================================== + +if [ "${DB_TYPE:-mysql}" = "mysql" ]; then + # Wait for MariaDB to be ready + echo -n "Waiting for MariaDB to be ready..." + DB_STARTED=0 + + for ((i=0; i < 300; i++)) + do + nc -z $DB_HOST 3306 && DB_STARTED=1 && break + echo -n "." + sleep 0.1 + done + + if [ "$DB_STARTED" != 1 ]; then + echo " [FAILED] MariaDB is not ready" + exit 1 + else + echo " [OK]" + fi +fi + +JAVA_MEMORY=${JAVA_MEMORY-"512m"} +JAVA_GC=${JAVA_GC:-"G1GC"} + +if [ "$JAVA_GC_LOG" = "1" ]; then + JAVA_GC_LOG="-Xlog:gc*:file=/srv/logs/gc-%t.log:tags,time,uptime,level:filecount=10,filesize=8M" +fi + +if [ -n "$JAVA_MEMORY" ]; then + JAVA_MEMORY="-Xms${JAVA_MEMORY} -Xmx${JAVA_MEMORY}" +fi + +JAVA_OPTS="$JAVA_MEMORY -XX:+Use${JAVA_GC} $JAVA_GC_LOG $JAVA_OPTS" + +echo "Starting Araknemu with java options: $JAVA_OPTS" + +java $JAVA_OPTS -jar araknemu.jar