Skip to content

Commit

Permalink
Merge pull request #341 from vincent4vx/feature-271-use-env-in-config
Browse files Browse the repository at this point in the history
feat(config): close #271 Handle .env and environment variable
  • Loading branch information
vincent4vx authored Apr 5, 2024
2 parents f86f79b + 8485fa8 commit 81592d3
Show file tree
Hide file tree
Showing 17 changed files with 793 additions and 25 deletions.
17 changes: 17 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
DB_HOST=127.0.0.1
DB_USER=araknemu
DB_PASSWORD=
DB_NAME=araknemu

CLIENT_VERSION=1.29.1
AUTH_PORT=4444

GAME_HOST=127.0.0.1
GAME_PORT=5555
TIMEZONE=Europe/Paris
PRELOAD=false

RATE_XP=1.0
RATE_DROP=1.0

ADMIN_COMMAND_SCRIPT_PATH=scripts/commands
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ config.ini
hs_err_*
replay_*
hotspot_*
.env
45 changes: 25 additions & 20 deletions config.ini.dist
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
; Server Configuration
; --------------------
; > The listened port for authentication server. Default 444
server.port = 4444
server.port = ${AUTH_PORT:-4444}

; Dofus client Configuration
; --------------------------
; > The required Dofus client version for logged in. Default "1.29.1"
client.version = 1.29.1
client.version = ${CLIENT_VERSION:-1.29.1}

; Password
; --------
Expand Down Expand Up @@ -83,26 +83,31 @@ client.version = 1.29.1
;connection_name.autoReconnect = true

; Configure database for authentication server
realm.host = 127.0.0.1
realm.user = araknemu
realm.dbname = araknemu
realm.type = ${DB_TYPE:-mysql}
realm.host = ${DB_HOST:-127.0.0.1}
realm.user = ${DB_USER:-araknemu}
realm.password = $DB_PASSWORD
realm.dbname = ${DB_NAME:-araknemu}
realm.path = ${DB_NAME:-araknemu.db}

; Configure database for game server
game.host = 127.0.0.1
game.user = araknemu
game.dbname = araknemu
game.type = ${DB_TYPE:-mysql}
game.host = ${DB_HOST:-127.0.0.1}
game.user = ${DB_USER:-araknemu}
game.password = $DB_PASSWORD
game.dbname = ${DB_NAME:-araknemu}

[game]
; Section for configure the game server

; Server Configuration
; --------------------
; > The listened port for game server. Default 5555
;server.port = 5555
server.port = ${GAME_PORT:-5555}
; > Get the server IP address. Default 127.0.0.1
;server.ip = 127.0.0.1
server.ip = ${GAME_HOST:-127.0.0.1}
; > The timezone for game server. Default Europe/Paris
;server.timezone = Europe/Paris
server.timezone = ${TIMEZONE:-Europe/Paris}


; Antispam & other
Expand Down Expand Up @@ -224,13 +229,13 @@ game.dbname = araknemu
; > Get the XP multiplier
; > The value should be a positive decimal number.
; > Default value : 1.0
;fight.rate.xp = 1.0
fight.rate.xp = ${XP_RATE:-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 = 1.0
fight.rate.drop = ${DROP_RATE:-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
Expand All @@ -253,9 +258,9 @@ game.dbname = araknemu
; > By default this value is true
; > In case of submodule (i.e. preload.service.submodule), the preload will be checked on each part.
; > So if "preload.service" is false, "preload.service.submodule" will be considered as false
;preload.map = false
;preload.monster = false
;preload.npc = false
preload.map = ${PRELOAD:-true}
preload.monster = ${PRELOAD:-true}
preload.npc = ${PRELOAD:-true}

[admin]
; Section for configure admin commands
Expand All @@ -272,7 +277,7 @@ game.dbname = araknemu
; > Get the commands scripts path
; > Should be a directory path
; > Default: "scripts/commands/[context]"
;account.scripts.path = scripts/commands/account
;player.scripts.path = scripts/commands/player
;debug.scripts.path = scripts/commands/debug
;server.scripts.path = scripts/commands/server
account.scripts.path = "${ADMIN_COMMAND_SCRIPT_PATH:-scripts/commands}/account"
player.scripts.path = "${ADMIN_COMMAND_SCRIPT_PATH:-scripts/commands}/player"
debug.scripts.path = "${ADMIN_COMMAND_SCRIPT_PATH:-scripts/commands}/debug"
server.scripts.path = "${ADMIN_COMMAND_SCRIPT_PATH:-scripts/commands}/server"
14 changes: 10 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
Expand All @@ -118,6 +123,7 @@
<version>9+181-r4173-1</version>
</dependency>


<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand Down Expand Up @@ -150,10 +156,10 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.3</version>
<!-- <configuration>-->
<!-- <parallel>classes</parallel>-->
<!-- <forkCount>4</forkCount>-->
<!-- </configuration>-->
<configuration>
<parallel>classes</parallel>
<forkCount>4</forkCount>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

package fr.quatrevieux.araknemu.core.config;

import fr.quatrevieux.araknemu.core.config.env.EnvDriver;
import io.github.cdimascio.dotenv.Dotenv;
import io.github.cdimascio.dotenv.DotenvBuilder;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
Expand All @@ -41,6 +44,8 @@ public final class ConfigurationLoader {
private final Path baseDirectory;

private @MonotonicNonNull Path configFile;
private boolean enableDotEnv = true;
private String dotEnvFile = ".env";

public ConfigurationLoader(Path baseDirectory, FileLoader[] loaders) {
this.baseDirectory = baseDirectory;
Expand Down Expand Up @@ -84,6 +89,35 @@ public ConfigurationLoader configFile(Path configFile) {
return this;
}

/**
* Enable or disable usage of .env file and environment variables
*
* If enabled, {@link EnvDriver} will be used, so syntax like ${VAR}
* will be replaced by the environment variable value
*
* @param enableDotEnv true to enable, false to disable
*
* @return the current instance
*/
public ConfigurationLoader enableDotEnv(boolean enableDotEnv) {
this.enableDotEnv = enableDotEnv;

return this;
}

/**
* Define the .env file name to load
*
* @param dotEnvFile The file name
*
* @return the current instance
*/
public ConfigurationLoader dotEnvFile(String dotEnvFile) {
this.dotEnvFile = dotEnvFile;

return this;
}

/**
* Register a new config file loader
*
Expand Down Expand Up @@ -120,13 +154,31 @@ public Configuration load() throws IOException {
}

private Optional<Configuration> load(Path file) throws IOException {
Optional<Driver> driver = loadDriver(file);

if (enableDotEnv) {
final Dotenv dotenv = new DotenvBuilder()
.filename(dotEnvFile)
.directory(baseDirectory.toAbsolutePath().toString())
.ignoreIfMalformed()
.ignoreIfMissing()
.load()
;

driver = driver.map(d -> new EnvDriver(d, dotenv));
}

return driver.map(DefaultConfiguration::new);
}

private Optional<Driver> loadDriver(Path file) throws IOException {
if (!Files.isRegularFile(file)) {
return Optional.empty();
}

for (FileLoader loader : loaders) {
if (loader.supports(file)) {
return Optional.of(new DefaultConfiguration(loader.load(file)));
return Optional.of(loader.load(file));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*
* Copyright (c) 2017-2024 Vincent Quatrevieux
*/

package fr.quatrevieux.araknemu.core.config.env;

import fr.quatrevieux.araknemu.core.config.Driver;
import fr.quatrevieux.araknemu.core.config.Pool;
import io.github.cdimascio.dotenv.Dotenv;

/**
* Decorate a generic driver to handle environment variables
* and bash style string interpolation.
*/
public final class EnvDriver implements Driver {
private final Driver driver;
private final Dotenv dotenv;

public EnvDriver(Driver driver, Dotenv dotenv) {
this.driver = driver;
this.dotenv = dotenv;
}

@Override
public boolean has(String key) {
return driver.has(key);
}

@Override
public Object get(String key) {
return driver.get(key);
}

@Override
public Pool pool(String key) {
return new EnvPool(
driver.pool(key),
dotenv
);
}
}
89 changes: 89 additions & 0 deletions src/main/java/fr/quatrevieux/araknemu/core/config/env/EnvPool.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*
* Copyright (c) 2017-2024 Vincent Quatrevieux
*/

package fr.quatrevieux.araknemu.core.config.env;

import fr.quatrevieux.araknemu.core.config.Pool;
import io.github.cdimascio.dotenv.Dotenv;
import org.apache.commons.lang3.tuple.Pair;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
* Handle interpolation of environment variables in a pool
*/
final class EnvPool implements Pool {
private final Pool pool;
private final Dotenv dotenv;
private final Map<String, String> cache = new HashMap<>();

public EnvPool(Pool pool, Dotenv dotenv) {
this.pool = pool;
this.dotenv = dotenv;
}

@Override
public boolean has(String key) {
return pool.has(key);
}

@Override
public @Nullable String get(String key) {
final @Nullable String cachedValue = cache.get(key);

if (cachedValue != null) {
return cachedValue;
}

final String rawValue = pool.get(key);

if (rawValue == null) {
return null;
}

final String value = interpolate(rawValue);

cache.put(key, value);

return value;
}

@Override
public List<String> getAll(String key) {
return pool.getAll(key).stream().map(this::interpolate).collect(Collectors.toList());
}

@Override
public Iterator<Map.Entry<String, String>> iterator() {
return StreamSupport.stream(pool.spliterator(), false)
.map(entry -> (Map.Entry<String, String>) Pair.of(entry.getKey(), interpolate(entry.getValue())))
.iterator()
;
}

private String interpolate(String value) {
return ExpressionParser.evaluate(value, dotenv::get);
}
}
Loading

0 comments on commit 81592d3

Please sign in to comment.