From b8ab80d1f373165239b3861b992e24411cc60310 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Mon, 25 Sep 2017 17:48:57 +0200 Subject: [PATCH] Sync containers from rroemhild and add instructions in README (#1655) --- Dockerfile | 17 ++- docker/README.md | 124 ++++++++++++------ docker/conf/ejabberd.yml.tpl | 7 +- .../docker-compose-cluster/Dockerfile | 3 + .../examples/docker-compose-cluster/README.md | 23 ++++ .../docker-compose-cluster/docker-compose.yml | 25 ++++ .../scripts/lib/functions.sh | 37 ++++++ .../scripts/pre/30_join_cluster.sh | 28 ++++ docker/examples/docker-compose/README.md | 1 + .../docker-compose/docker-compose.yml | 11 ++ docker/scripts/lib/base_config.sh | 4 +- 11 files changed, 231 insertions(+), 49 deletions(-) create mode 100644 docker/examples/docker-compose-cluster/Dockerfile create mode 100644 docker/examples/docker-compose-cluster/README.md create mode 100644 docker/examples/docker-compose-cluster/docker-compose.yml create mode 100644 docker/examples/docker-compose-cluster/scripts/lib/functions.sh create mode 100755 docker/examples/docker-compose-cluster/scripts/pre/30_join_cluster.sh create mode 100644 docker/examples/docker-compose/README.md create mode 100644 docker/examples/docker-compose/docker-compose.yml diff --git a/Dockerfile b/Dockerfile index e5f3d78f3..b9908e24c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ ENV EJABBERD_BRANCH=17.08 \ EJABBERD_HOME=/opt/ejabberd \ EJABBERD_DEBUG_MODE=false \ HOME=$EJABBERD_HOME \ - PATH=$EJABBERD_HOME/bin:/usr/sbin:/usr/bin:/sbin:/bin \ + PATH=$EJABBERD_HOME/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/sbin \ DEBIAN_FRONTEND=noninteractive \ XMPP_DOMAIN=localhost \ # Set default locale for the environment @@ -38,6 +38,7 @@ RUN set -x \ erlang-src erlang-dev \ ' \ && requiredAptPackages=' \ + wget \ locales \ ldnsutils \ python2.7 \ @@ -47,7 +48,7 @@ RUN set -x \ erlang-base erlang-snmp erlang-ssl erlang-ssh erlang-webtool \ erlang-tools erlang-xmerl erlang-corba erlang-diameter erlang-eldap \ erlang-eunit erlang-ic erlang-odbc erlang-os-mon \ - erlang-parsetools erlang-percept erlang-typer erlang-inets \ + erlang-parsetools erlang-percept erlang-typer \ python-mysqldb \ imagemagick \ ' \ @@ -68,7 +69,6 @@ RUN set -x \ && chmod +x ./autogen.sh \ && ./autogen.sh \ && ./configure --enable-user=$EJABBERD_USER \ - --prefix=/ \ --enable-all \ --disable-tools \ --disable-pam \ @@ -82,12 +82,19 @@ RUN set -x \ && mkdir $EJABBERD_HOME/module_source \ && cd $EJABBERD_HOME \ && rm -rf /tmp/ejabberd \ - && rm -rf /etc/ejabberd \ - && ln -sf $EJABBERD_HOME/conf /etc/ejabberd \ + && rm -rf /usr/local/etc/ejabberd \ + && ln -sf $EJABBERD_HOME/conf /usr/local/etc/ejabberd \ && chown -R $EJABBERD_USER: $EJABBERD_HOME \ && rm -rf /var/lib/apt/lists/* \ && apt-get purge -y --auto-remove $buildDeps +RUN wget -P /usr/local/share/ca-certificates/cacert.org http://www.cacert.org/certs/root.crt http://www.cacert.org/certs/class3.crt; \ + update-ca-certificates + +# Create logging directories +RUN mkdir -p /var/log/ejabberd +RUN touch /var/log/ejabberd/crash.log /var/log/ejabberd/error.log /var/log/ejabberd/erlang.log + # Wrapper for setting config on disk from environment # allows setting things like XMPP domain at runtime ADD ./docker/run.sh /sbin/run diff --git a/docker/README.md b/docker/README.md index f329b89fc..443a6b4a3 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,21 +1,23 @@ ejabberd container - [Introduction](#introduction) + - [Version](#version) - [Quick Start](#quick-start) - [Usage](#usage) - [Persistence](#persistence) - [SSL Certificates](#ssl-certificates) - [Base Image](#base-image) -- [Ejabberd Configuration](#ejabberd-configuration) + - [Cluster Example](#cluster-example) +- [Runtime Configuration](#runtime-configuration) - [Served Hostnames](#served-hostnames) - [Authentication](#authentication) - [Admins](#admins) - [Users](#users) - [SSL](#ssl) + - [Erlang](#erlang) - [Modules](#modules) - [Logging](#logging) - [Mount Configurations](#mount-configurations) - - [Erlang Configuration](#erlang-configuration) - [Maintenance](#maintenance) - [Register Users](#register-users) - [Creating Backups](#creating-backups) @@ -28,11 +30,29 @@ ejabberd container # Introduction -Dockerfile to build an [ejabberd](https://www.ejabberd.im/) container image. +This [ejabberd][] docker container is based on the work done by [rroemhild][]. See more [in this blogpost][]. +This container includes the necessary files to build your own containerized ejabberd, +but *IT IS NOT* used to generate official images on the docker [hub][]. +This container is not maintained by [ProcessOne][]. -Docker Tag Names are based on ejabberd versions in git [tags][]. The image tag `:latest` is based on the master branch. +[ProcessOne][] provides and maintain official containers on the docker [hub][], which targets developers for now and will becomes production ready in a near future. +These [new containers] allow to build and run ejabberd in a simple and lightweight environment. -[tags]: https://github.com/rroemhild/ejabberd/tags +[ejabberd]: https://www.ejabberd.im/ +[rroemhild]: https://github.com/rroemhild/docker-ejabberd/ +[in this blogpost]: https://blog.process-one.net/ejabberd-16-12/ +[hub]: https://hub.docker.com/r/ejabberd/ecs/ +[new containers]: https://github.com/processone/docker-ejabberd/ +[ProcessOne]: https://www.process-one.net/ + +## Version + +Current Version: `17.08` + +Docker Tag Names are based on ejabberd versions in git [branches][] and [tags][]. The image tag `:latest` is based on the master branch. + +[tags]: https://github.com/rroemhild/docker-ejabberd/tags +[branches]: https://github.com/rroemhild/docker-ejabberd/branches # Quick Start @@ -46,13 +66,20 @@ docker run -d \ -p 5280:5280 \ -h 'xmpp.example.de' \ -e "XMPP_DOMAIN=example.de" \ - -e "ERLANG_NODE=nodename" \ + -e "ERLANG_NODE=ejabberd" \ -e "EJABBERD_ADMINS=admin@example.de admin2@example.de" \ -e "EJABBERD_USERS=admin@example.de:password1234 admin2@example.de" \ -e "TZ=Europe/Berlin" \ rroemhild/ejabberd ``` +or with the [docker-compose](examples/docker-compose/docker-compose.yml) example + +```bash +wget https://raw.githubusercontent.com/rroemhild/docker-ejabberd/master/examples/docker-compose/docker-compose.yml +docker-compose up +``` + # Usage ## Persistence @@ -100,7 +127,11 @@ ADD ./example.com.pem /opt/ejabberd/ssl/example.com.pem If you need root privileges switch to `USER root` and go back to `USER ejabberd` when you're done. -# Ejabberd Configuration +## Cluster Example + +The [docker-compose-cluster](examples/docker-compose-cluster) example demonstrates how to extend this container image to setup a multi-master cluster. + +# Runtime Configuration You can additionally provide extra runtime configuration in a downstream image by replacing the config template `ejabberd.yml.tpl` with one based on this image's template and include extra interpolation of environment variables. The template is parsed by Jinja2 with the runtime environment (equivalent to Python's `os.environ` available as `env`). @@ -138,6 +169,26 @@ EJABBERD_EXTAUTH_CACHE=600 ``` **EJABBERD_EXTAUTH_INSTANCES** must be an integer with a minimum value of 1. **EJABBERD_EXTAUTH_CACHE** can be set to "false" or an integer value representing cache time in seconds. Note that caching should not be enabled if internal auth is also enabled. +### Password format + +The variable `EJABBERD_AUTH_PASSWORD_FORMAT` controls in which format user passwords are +stored. Possible values are `plain` and `scram`. The default is to store +[SCRAM](https://en.wikipedia.org/wiki/Salted_Challenge_Response_Authentication_Mechanism)bled +passwords, meaning that it is impossible to obtain the original plain password from the +stored information. + +NOTE: SCRAM does not work with SIP/TURN foreign authentication methods. In this case, you +may have to disable the option. More details can be found here: +https://docs.ejabberd.im/admin/configuration/#internal + +If using SCRAM with an SQL database that has plaintext passwords stored, use the command + +``` +ejabberdctl convert_to_scram example.org +``` + +to convert all your existing plaintext passwords to scrambled format. + ### MySQL Authentication Set `EJABBERD_AUTH_METHOD=external` and `EJABBERD_EXTAUTH_PROGRAM=/opt/ejabberd/scripts/lib/auth_mysql.py` to enable MySQL authentication. Use the following environment variables to configure the database connection and the layout of the database. Password changing, registration, and unregistration are optional features and are enabled only if the respective queries are provided. @@ -215,28 +266,38 @@ EJABBERD_USERS=admin@example.ninja:password1234 user1@test.com user1@xyz.io ``` ## SSL - +- **EJABBERD_SKIP_MAKE_SSLCERT**: Skip generating ssl certificates. Default: false - **EJABBERD_SSLCERT_HOST**: SSL Certificate for the hostname. - **EJABBERD_SSLCERT_EXAMPLE_COM**: SSL Certificates for XMPP domains. -- **EJABBERD_STARTTLS**: Set to `false` to disable StartTLS for client to server connections. Default: `true`. -- **EJABBERD_S2S_SSL**: Set to `false` to disable SSL in server 2 server connections. Default: `true`. -- **EJABBERD_HTTPS**: If your proxy terminates SSL you may want to disable HTTPS on port 5280 and 5443. Default: `true`. -- **EJABBERD_PROTOCOL_OPTIONS_TLSV1**: Allow TLSv1 protocol. Default: `false`. -- **EJABBERD_PROTOCOL_OPTIONS_TLSV1_1**: Allow TLSv1.1 protocol. Default: `true`. -- **EJABBERD_CIPHERS**: Cipher suite. Default: `HIGH:!aNULL:!3DES`. -- **EJABBERD_DHPARAM**: Set to `true` to use or generate custom DH parameters. Default: `false`. +- **EJABBERD_STARTTLS**: Set to `false` to disable StartTLS for client to server connections. Defaults + to `true`. +- **EJABBERD_S2S_SSL**: Set to `false` to disable SSL in server 2 server connections. Defaults to `true`. +- **EJABBERD_HTTPS**: If your proxy terminates SSL you may want to disable HTTPS on port 5280 and 5443. Defaults to `true`. +- **EJABBERD_PROTOCOL_OPTIONS_TLSV1**: Allow TLSv1 protocol. Defaults to `false`. +- **EJABBERD_PROTOCOL_OPTIONS_TLSV1_1**: Allow TLSv1.1 protocol. Defaults to `true`. +- **EJABBERD_CIPHERS**: Cipher suite. Defaults to `HIGH:!aNULL:!3DES`. +- **EJABBERD_DHPARAM**: Set to `true` to use or generate custom DH parameters. Defaults to `false`. +- **EJABBERD_SKIP_MAKE_DHPARAM**: Skip generating DH params. Default: false + +## Erlang +- **ERLANG_NODE**: Allows to explicitly specify erlang node for ejabberd. Set to `ejabberd` lets erlang add the hostname. Defaults to `ejabberd@localhost`. +- **ERLANG_COOKIE**: Set erlang cookie. Defaults to auto-generated cookie. +- **ERLANG_OPTIONS**: Overwrite additional options passed to erlang while starting ejabberd. ## Modules - -- **EJABBERD_SKIP_MODULES_UPDATE**: If you do not need to update ejabberd modules specs, skip the update task and speedup start. Default: `false`. -- **EJABBERD_MOD_MUC_ADMIN**: Activate the mod_muc_admin module. Default: `false`. -- **EJABBERD_MOD_ADMIN_EXTRA**: Activate the mod_muc_admin module. Default: `true`. -- **EJABBERD_REGISTER_TRUSTED_NETWORK_ONLY**: Only allow user registration from the trusted_network access rule. Default: `true`. -- **EJABBERD_MOD_VERSION**: Activate the mod_version module. Default: `true`. +- **EJABBERD_SKIP_MODULES_UPDATE**: If you do not need to update ejabberd modules specs, skip the update task and speedup start. Defaults to `false`. +- **EJABBERD_MOD_MUC_ADMIN**: Activate the mod_muc_admin module. Defaults to `false`. +- **EJABBERD_MOD_ADMIN_EXTRA**: Activate the mod_muc_admin module. Defaults to `true`. +- **EJABBERD_REGISTER_TRUSTED_NETWORK_ONLY**: Only allow user registration from the trusted_network access rule. Defaults to `true`. +- **EJABBERD_MOD_VERSION**: Activate the mod_version module. Defaults to `true`. +- **EJABBERD_SOURCE_MODULES**: List of modules, which will be installed from sources localized in ${EJABBERD_HOME}/module_source. +- **EJABBERD_CONTRIB_MODULES**: List of modules, which will be installed from contrib repository. +- **EJABBERD_RESTART_AFTER_MODULE_INSTALL**: If any modules were installed, restart the server, if the option is enabled. +- **EJABBERD_CUSTOM_AUTH_MODULE_OVERRIDE**: If a custom module was defined for handling auth, we need to override the pre-defined auth methods in the config. ## Logging -Use the **EJABBERD_LOGLEVEL** environment variable to set verbosity. Default: `4` (Info). +Use the **EJABBERD_LOGLEVEL** environment variable to set verbosity. Defaults to `4` (Info). ``` loglevel: Verbosity of log files generated by ejabberd. @@ -276,25 +337,6 @@ Example configuration files can be downloaded from the ejabberd [github](https:/ When these files exist in ```/opt/ejabberd/conf```, the run script will ignore the configuration templates. -## Erlang Configuration - -With the following environment variables you can configure options that are passed by ejabberdctl to the erlang runtime system when starting ejabberd. - -- **POLL**: Set to `false` to disable Kernel polling. Default: `true`. -- **SMP**: SMP support `enable`, `auto`, `disable`. Default: `auto`. -- **ERL_MAX_PORTS**: Maximum number of simultaneously open Erlang ports. Default: `32000`. -- **FIREWALL_WINDOW**: Range of allowed ports to pass through a firewall. Default: `not defined`. -- **INET_DIST_INTERFACE**: IP address where this Erlang node listens other nodes. Default: `0.0.0.0`. -- **ERL_EPMD_ADDRESS**: IP addresses where epmd listens for connections. Default: `0.0.0.0`. -- **ERL_PROCESSES**: Maximum number of Erlang processes. Default: `250000`. -- **ERL_MAX_ETS_TABLES**: Maximum number of Erlang processes. Default: `1400`. -- **ERLANG_OPTIONS**: Overwrite additional options passed to erlang while starting ejabberd. Default: `-noshell` -- **ERLANG_NODE**: Allows to explicitly specify erlang node for ejabberd. Set to `nodename` lets erlang add the hostname. Default: `ejabberd@localhost`. -- **EJABBERD_CONFIG_PATH**: ejabberd configuration file. Default: `/opt/ejabberd/conf/ejabberd.yml`. -- **CONTRIB_MODULES_PATH**: contributed ejabberd modules path. Default: `/opt/ejabberd/modules`. -- **CONTRIB_MODULES_CONF_DIR**: configuration directory for contributed modules. Default: `/opt/ejabberd/modules/conf`. -- **ERLANG_COOKIE**: Set erlang cookie. Default is to auto-generated cookie. - # Maintenance The `ejabberdctl` command is in the search path and can be run by: diff --git a/docker/conf/ejabberd.yml.tpl b/docker/conf/ejabberd.yml.tpl index e90e7d7c8..cb17460e5 100644 --- a/docker/conf/ejabberd.yml.tpl +++ b/docker/conf/ejabberd.yml.tpl @@ -123,8 +123,10 @@ auth_method: - {{ auth_method }} {%- endfor %} +auth_password_format: {{ env.get('EJABBERD_AUTH_PASSWORD_FORMAT', 'scram') }} + {%- if 'anonymous' in env.get('EJABBERD_AUTH_METHOD', 'internal').split() %} -anonymous_protocol: login_anon +anonymous_protocol: both allow_multiple_connections: true {%- endif %} @@ -347,6 +349,8 @@ modules: - "flat" - "hometree" - "pep" # pep requires mod_caps + mod_push: {} + mod_push_keepalive: {} mod_register: ## ## Protect In-Band account registrations with CAPTCHA. @@ -432,5 +436,6 @@ redis_port: {{ env['EJABBERD_REDIS_PORT'] or 6379 }} redis_password: {{ env['EJABBERD_REDIS_PASSWORD'] }} {% endif %} redis_db: {{ env['EJABBERD_REDIS_DB'] or 0}} +redis_reconnect_timeout: {{ env['EJABBERD_REDIS_RECONNECT_TIMEOUT'] or 1 }} redis_connect_timeout: {{ env['EJABBERD_REDIS_CONNECT_TIMEOUT'] or 1 }} {% endif %} diff --git a/docker/examples/docker-compose-cluster/Dockerfile b/docker/examples/docker-compose-cluster/Dockerfile new file mode 100644 index 000000000..43a715dc1 --- /dev/null +++ b/docker/examples/docker-compose-cluster/Dockerfile @@ -0,0 +1,3 @@ +FROM rroemhild/ejabberd +ENV EJABBERD_HOME /opt/ejabberd +COPY ./scripts $EJABBERD_HOME/scripts diff --git a/docker/examples/docker-compose-cluster/README.md b/docker/examples/docker-compose-cluster/README.md new file mode 100644 index 000000000..0a50e3b15 --- /dev/null +++ b/docker/examples/docker-compose-cluster/README.md @@ -0,0 +1,23 @@ +# Ejabberd cluster with docker compose + +This example uses [dnsdocker](https://github.com/tonistiigi/dnsdock) to discover other nodes and setup a multi-master cluster. + +Build the ejabberd cluster image: + +```bash +git clone https://github.com/rroemhild/docker-ejabberd.git +cd docker-ejabberd/examples/docker-compose-cluster +docker-compose build +``` + +Start dnsdocker and the first ejabberd node: + +```bash +docker-compose up -d +``` + +Wait until the first ejabberd node is up and running `docker-compose logs ejabberd`, then add some ejabberd nodes to the cluster: + +```bash +docker-compose scale ejabberd=4 +``` diff --git a/docker/examples/docker-compose-cluster/docker-compose.yml b/docker/examples/docker-compose-cluster/docker-compose.yml new file mode 100644 index 000000000..1db62aa13 --- /dev/null +++ b/docker/examples/docker-compose-cluster/docker-compose.yml @@ -0,0 +1,25 @@ +dnsdock: + image: tonistiigi/dnsdock + volumes: + - /var/run/docker.sock:/var/run/docker.sock + ports: + - 172.17.42.1:53:53/udp + +ejabberd: + build: . + ports: + - 5222 + - 5269 + - 5280 + environment: + - XMPP_DOMAIN=example.com + - ERLANG_NODE=ejabberd + - EJABBERD_ADMINS=admin@example.com + - EJABBERD_USERS=admin@example.com:test321 user@example.com + - ERLANG_COOKIE=testCluster + - SKIP_MODULES_UPDATE=true + - EJABBERD_CLUSTER=true + - USE_DNS=true + dns: 172.17.42.1 + domainname: dockercomposecluster_ejabberd.docker + tty: true diff --git a/docker/examples/docker-compose-cluster/scripts/lib/functions.sh b/docker/examples/docker-compose-cluster/scripts/lib/functions.sh new file mode 100644 index 000000000..2ff16f659 --- /dev/null +++ b/docker/examples/docker-compose-cluster/scripts/lib/functions.sh @@ -0,0 +1,37 @@ +# overwrite get_nodename to discover hostname from DNS +get_nodename() { + local hostname=${HOSTNAME} + + # get hostname from dns + if ( is_true ${USE_DNS} ); then + # wait for dns registration + sleep 1 + + nodename=$(discover_dns_hostname ${HOSTIP}) + + is_set ${nodename} \ + && hostname=${nodename} + fi + + echo $hostname + return 0 +} + + +# discover hostname from dns with a reverse lookup +discover_dns_hostname() { + local hostip=$1 + + # try to get the hostname from dns + local dnsname=$(drill -x ${hostip} \ + | grep PTR \ + | awk '{print $5}' \ + | grep -E "^[a-zA-Z0-9]+([-._]?[a-zA-Z0-9]+)*.[a-zA-Z]+\.$" \ + | cut -d '.' -f1 \ + | tail -1) + + is_set ${dnsname} \ + && echo ${dnsname} + + return 0 +} diff --git a/docker/examples/docker-compose-cluster/scripts/pre/30_join_cluster.sh b/docker/examples/docker-compose-cluster/scripts/pre/30_join_cluster.sh new file mode 100755 index 000000000..f68e22eec --- /dev/null +++ b/docker/examples/docker-compose-cluster/scripts/pre/30_join_cluster.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -e + +source "${EJABBERD_HOME}/scripts/lib/base_config.sh" +source "${EJABBERD_HOME}/scripts/lib/config.sh" +source "${EJABBERD_HOME}/scripts/lib/base_functions.sh" +source "${EJABBERD_HOME}/scripts/lib/functions.sh" + + +get_cluster_node_from_dns() { + local cluster_host=$(drill ${DOMAINNAME} \ + | grep ${DOMAINNAME} \ + | grep -v ${HOSTIP} \ + | awk '{print $5}' \ + | grep -v "^$" \ + | head -1) + echo $(discover_dns_hostname ${cluster_host}) +} + + +file_exist ${FIRST_START_DONE_FILE} \ + && exit 0 + + +join_cluster $(get_cluster_node_from_dns) + + +exit 0 diff --git a/docker/examples/docker-compose/README.md b/docker/examples/docker-compose/README.md new file mode 100644 index 000000000..597e051b3 --- /dev/null +++ b/docker/examples/docker-compose/README.md @@ -0,0 +1 @@ +# simple docker-compose example diff --git a/docker/examples/docker-compose/docker-compose.yml b/docker/examples/docker-compose/docker-compose.yml new file mode 100644 index 000000000..1f547ee8f --- /dev/null +++ b/docker/examples/docker-compose/docker-compose.yml @@ -0,0 +1,11 @@ +ejabberd: + image: rroemhild/ejabberd + ports: + - 5222:5222 + - 5269:5269 + - 5280:5280 + environment: + - ERLANG_NODE=ejabberd + - XMPP_DOMAIN=example.com xyz.io + - EJABBERD_ADMINS=admin@example.com + - EJABBERD_USERS=admin@example.com:password4321 user1@xyz.io diff --git a/docker/scripts/lib/base_config.sh b/docker/scripts/lib/base_config.sh index a856fe785..9f66a4ae5 100644 --- a/docker/scripts/lib/base_config.sh +++ b/docker/scripts/lib/base_config.sh @@ -3,7 +3,7 @@ readonly HOSTNAME=$(hostname -f) readonly DOMAINNAME=$(hostname -d) readonly ERLANGCOOKIEFILE="${EJABBERD_HOME}/.erlang.cookie" -readonly EJABBERDCTL="/sbin/ejabberdctl" +readonly EJABBERDCTL="/usr/local/sbin/ejabberdctl" readonly CONFIGFILE="${EJABBERD_HOME}/conf/ejabberd.yml" readonly CONFIGTEMPLATE="${EJABBERD_HOME}/conf/ejabberd.yml.tpl" readonly CTLCONFIGFILE="${EJABBERD_HOME}/conf/ejabberdctl.cfg" @@ -11,7 +11,7 @@ readonly CTLCONFIGTEMPLATE="${EJABBERD_HOME}/conf/ejabberdctl.cfg.tpl" readonly SSLCERTDIR="${EJABBERD_HOME}/ssl" readonly SSLCERTHOST="${SSLCERTDIR}/host.pem" readonly SSLDHPARAM="${SSLCERTDIR}/dh.pem" -readonly LOGDIR="/var/log/ejabberd" +readonly LOGDIR="/usr/local/var/log/ejabberd" readonly FIRST_START_DONE_FILE="/${EJABBERD_HOME}/first-start-done" readonly CLUSTER_NODE_FILE="/${EJABBERD_HOME}/cluster-done"