diff --git a/.gitignore b/.gitignore index fa587c4d..1af7f1de 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,6 @@ _site/ /website/site/templates/shortcodes/server.conf /website/site/templates/shortcodes/sample-exim.conf /website/site/templates/shortcodes/joex.conf +/website/site/templates/shortcodes/config.env.txt /docker/docs /docker/dev-log diff --git a/build.sbt b/build.sbt index 2d22c7b3..82ebe6c2 100644 --- a/build.sbt +++ b/build.sbt @@ -709,7 +709,6 @@ val website = project val templateOut = baseDirectory.value / "site" / "templates" / "shortcodes" val staticOut = baseDirectory.value / "site" / "static" / "openapi" IO.createDirectories(Seq(templateOut, staticOut)) - val logger = streams.value.log val files = Seq( (restserver / Compile / resourceDirectory).value / "reference.conf" -> templateOut / "server.conf", @@ -721,6 +720,17 @@ val website = project IO.copy(files) files.map(_._2) }.taskValue, + Compile / resourceGenerators += Def.task { + val templateOut = + baseDirectory.value / "site" / "templates" / "shortcodes" / "config.env.txt" + val files = List( + (restserver / Compile / resourceDirectory).value / "reference.conf", + (joex / Compile / resourceDirectory).value / "reference.conf" + ) + val cfg = EnvConfig.makeConfig(files) + EnvConfig.serializeTo(cfg, templateOut) + Seq(templateOut) + }.taskValue, Compile / resourceGenerators += Def.task { val changelog = (LocalRootProject / baseDirectory).value / "Changelog.md" val targetDir = baseDirectory.value / "site" / "content" / "docs" / "changelog" diff --git a/modules/joex/src/main/resources/reference.conf b/modules/joex/src/main/resources/reference.conf index 4313771a..80348f57 100644 --- a/modules/joex/src/main/resources/reference.conf +++ b/modules/joex/src/main/resources/reference.conf @@ -20,14 +20,19 @@ docspell.joex { # The database connection. # - # By default a H2 file-based database is configured. You can provide - # a postgresql or mariadb connection here. When using H2 use the - # PostgreSQL compatibility mode and AUTO_SERVER feature. - # # It must be the same connection as the rest server is using. jdbc { + + # The JDBC url to the database. By default a H2 file-based + # database is configured. You can provide a postgresql or mariadb + # connection here. When using H2 use the PostgreSQL compatibility + # mode and AUTO_SERVER feature. url = "jdbc:h2://"${java.io.tmpdir}"/docspell-demo.db;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;AUTO_SERVER=TRUE" + + # The database user. user = "sa" + + # The database password. password = "" } diff --git a/modules/restserver/src/main/resources/reference.conf b/modules/restserver/src/main/resources/reference.conf index 074c4e39..961d7d46 100644 --- a/modules/restserver/src/main/resources/reference.conf +++ b/modules/restserver/src/main/resources/reference.conf @@ -280,6 +280,7 @@ docspell.server { # Configuration for the backend. backend { + # Enable or disable debugging for e-mail related functionality. This # applies to both sending and receiving mails. For security reasons # logging is not very extensive on authentication failures. Setting @@ -287,13 +288,17 @@ docspell.server { mail-debug = false # The database connection. - # - # By default a H2 file-based database is configured. You can - # provide a postgresql or mariadb connection here. When using H2 - # use the PostgreSQL compatibility mode and AUTO_SERVER feature. jdbc { + # The JDBC url to the database. By default a H2 file-based + # database is configured. You can provide a postgresql or + # mariadb connection here. When using H2 use the PostgreSQL + # compatibility mode and AUTO_SERVER feature. url = "jdbc:h2://"${java.io.tmpdir}"/docspell-demo.db;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;AUTO_SERVER=TRUE" + + # The database user. user = "sa" + + # The database password. password = "" } diff --git a/project/EnvConfig.scala b/project/EnvConfig.scala new file mode 100644 index 00000000..ea040adb --- /dev/null +++ b/project/EnvConfig.scala @@ -0,0 +1,75 @@ +import sbt._ +import com.typesafe.config._ + +import scala.annotation.tailrec +import scala.jdk.CollectionConverters._ +import java.util.{Map => JMap} + +object EnvConfig { + def serializeTo(cfg: Config, out: File): Unit = + IO.write(out, serialize(cfg)) + + def serialize(cfg: Config): String = { + val buffer = new StringBuilder + buffer.append("#### Server Configuration ####\n") + for ( + entry <- cfg.entrySet().asScala.toList.sortBy(_.getKey) + if isValidKey("docspell.server", entry) + ) append(buffer, entry.getKey, entry.getValue) + + buffer.append("\n#### JOEX Configuration ####\n") + for ( + entry <- cfg.entrySet().asScala.toList.sortBy(_.getKey) + if isValidKey("docspell.joex", entry) + ) append(buffer, entry.getKey, entry.getValue) + + buffer.toString().trim + } + + private def append(buffer: StringBuilder, key: String, value: ConfigValue): Unit = { + if (value.origin().comments().asScala.nonEmpty) { + buffer.append("\n") + } + value + .origin() + .comments() + .forEach(c => buffer.append("# ").append(c).append("\n")) + buffer.append(keyToEnv(key)).append("=").append(value.render()).append("\n") + } + + def isValidKey(prefix: String, entry: JMap.Entry[String, ConfigValue]): Boolean = + entry.getKey + .startsWith(prefix) && entry.getValue.valueType() != ConfigValueType.LIST + + def makeConfig(files: List[File]): Config = + files + .foldLeft(ConfigFactory.empty()) { (cfg, file) => + val cf = ConfigFactory.parseFile(file) + cfg.withFallback(cf) + } + .withFallback(ConfigFactory.defaultOverrides(getClass.getClassLoader)) + .resolve() + + def makeConfig(file: File, files: File*): Config = + makeConfig(file :: files.toList) + + def keyToEnv(k: String): String = { + val buffer = new StringBuilder() + val len = k.length + + @tailrec + def go(current: Int): String = + if (current >= len) buffer.toString() + else { + k.charAt(current) match { + case '.' => buffer.append("_") + case '-' => buffer.append("__") + case '_' => buffer.append("___") + case c => buffer.append(c.toUpper) + } + go(current + 1) + } + + go(0) + } +} diff --git a/project/build.sbt b/project/build.sbt new file mode 100644 index 00000000..18e1242b --- /dev/null +++ b/project/build.sbt @@ -0,0 +1,2 @@ +libraryDependencies ++= + Seq("com.typesafe" % "config" % "1.4.1") diff --git a/website/site/content/docs/configure/_index.md b/website/site/content/docs/configure/_index.md index 7ebb0950..5eef1bb5 100644 --- a/website/site/content/docs/configure/_index.md +++ b/website/site/content/docs/configure/_index.md @@ -8,10 +8,11 @@ mktoc = true +++ Docspell's executables (restserver and joex) can take one argument – a -configuration file. If that is not given, the defaults are used. The -config file overrides default values, so only values that differ from -the defaults are necessary. The complete default options and their -documentation is at the end of this page. +configuration file. If that is not given, the defaults are used, +overriden by environment variables. A config file overrides default +values, so only values that differ from the defaults are necessary. +The complete default options and their documentation is at the end of +this page. Besides the config file, another way is to provide individual settings via key-value pairs to the executable by the `-D` option. For example @@ -22,6 +23,21 @@ the recommended way is to maintain a config file. If these options *and* a file is provded, then any setting given via the `-D…` option overrides the same setting from the config file. +At last, it is possible to configure docspell via environment +variables if there is no config file supplied (if a config file *is* +supplied, it is always preferred). Note that this approach is limited, +as arrays are not supported. A list of environment variables can be +found at the [end of this page](#environment-variables). The +environment variable name follows the corresponding config key - where +dots are replaced by underscores and dashes are replaced by two +underscores. For example, the config key `docspell.server.app-name` +can be defined as env variable `DOCSPELL_SERVER_APP__NAME`. + +It is also possible to specify environment variables inside a config +file (to get a mix of both) - please see the [documentation of the +config library](https://github.com/lightbend/config#standard-behavior) +for more on this. + # File Format The format of the configuration files can be @@ -31,9 +47,9 @@ library](https://github.com/lightbend/config) understands. The default values below are in HOCON format, which is recommended, since it allows comments and has some [advanced features](https://github.com/lightbend/config#features-of-hocon). -Please refer to their documentation for more on this. +Please also see their documentation for more details. -A short description (please see the links for better understanding): +A short description (please check the links for better understanding): The config consists of key-value pairs and can be written in a JSON-like format (called HOCON). Keys are organized in trees, and a key defines a full path into the tree. There are two ways: @@ -633,3 +649,11 @@ statements with level "DEBUG" will be printed, too. {{ incl_conf(path="templates/shortcodes/joex.conf") }} + +## Environment Variables + +Environment variables can be used when there is no config file +supplied. The listing below shows all possible variables and their +default values. + +{{ incl_conf(path="templates/shortcodes/config.env.txt") }}