diff --git a/.github/container/Dockerfile b/.github/container/Dockerfile
index 2a44b9035..ae2cc7311 100644
--- a/.github/container/Dockerfile
+++ b/.github/container/Dockerfile
@@ -1,20 +1,22 @@
#' Define default build variables
-## specifc ARGs for METHOD='direct'
ARG OTP_VSN='27.2'
ARG ELIXIR_VSN='1.18.1'
-## specifc ARGs for METHOD='package'
-ARG ALPINE_VSN='3.19'
-## general ARGs
ARG UID='9000'
ARG USER='ejabberd'
ARG HOME="opt/$USER"
-ARG METHOD='direct'
ARG BUILD_DIR="/$USER"
ARG VERSION='master'
################################################################################
-#' METHOD='direct' - build and install ejabberd directly from source
-FROM docker.io/erlang:${OTP_VSN}-alpine AS direct
+#' Compile ejabberdapi
+FROM docker.io/golang:1.23-alpine AS api
+RUN go install -v \
+ github.com/processone/ejabberd-api/cmd/ejabberd@master \
+ && mv bin/ejabberd bin/ejabberdapi
+
+################################################################################
+#' build and install ejabberd directly from source
+FROM docker.io/erlang:${OTP_VSN}-alpine AS ejabberd
RUN apk -U add --no-cache \
autoconf \
@@ -39,7 +41,8 @@ ARG ELIXIR_VSN
RUN wget -O - https://github.com/elixir-lang/elixir/archive/v$ELIXIR_VSN.tar.gz \
| tar -xzf -
-WORKDIR elixir-$ELIXIR_VSN
+WORKDIR /elixir-$ELIXIR_VSN
+ENV ERL_FLAGS="+JPperf true"
RUN make install clean
RUN mix local.hex --force \
@@ -50,6 +53,7 @@ COPY / $BUILD_DIR/
WORKDIR $BUILD_DIR
RUN mv .github/container/ejabberdctl.template . \
+ && mv .github/container/ejabberd.yml.example . \
&& ./autogen.sh \
&& ./configure --with-rebar=mix --enable-all \
&& make deps \
@@ -67,36 +71,18 @@ RUN cp -p $BUILD_DIR/tools/captcha*.sh $HOME-$VERSION/lib
RUN find "$HOME-$VERSION/bin" -name 'ejabberd' -delete \
&& find "$HOME-$VERSION/releases" -name 'COOKIE' -delete
-RUN wget -O "$HOME/conf/cacert.pem" 'https://curl.se/ca/cacert.pem' \
- && sed -i '/^loglevel:/a \ \
- \nca_file: /opt/ejabberd/conf/cacert.pem \
- \ncertfiles: \
- \n - /opt/ejabberd/conf/server.pem' "$HOME/conf/ejabberd.yml"
+RUN wget -O "$HOME/conf/cacert.pem" 'https://curl.se/ca/cacert.pem'
-################################################################################
-#' METHOD='package' - install ejabberd from binary tarball package
-FROM docker.io/alpine:${ALPINE_VSN} AS package
-COPY tarballs/ejabberd-*-linux-musl-*.tar.gz /tmp/
-WORKDIR /rootfs
-ARG HOME
-RUN home_root_dir=$(echo $HOME | sed 's|\(.*\)/.*|\1 |') \
- && mkdir -p $home_root_dir \
- && ARCH=$(uname -m | sed -e 's/x86_64/x64/;s/aarch64/arm64/') \
- && tar -xzf /tmp/ejabberd-*-linux-musl-$ARCH.tar.gz -C $home_root_dir
-
-################################################################################
#' Prepare ejabberd for runtime
-FROM ${METHOD} AS ejabberd
RUN apk -U add --no-cache \
git \
libcap \
openssl
-WORKDIR /rootfs
-ARG HOME
RUN mkdir -p usr/local/bin $HOME/conf $HOME/database $HOME/logs $HOME/upload
-ARG BUILD_DIR
+COPY --from=api /go/bin/ejabberdapi usr/local/bin/
+
RUN if [ ! -d $HOME/.ejabberd-modules ]; \
then \
if [ -d $BUILD_DIR/.ejabberd-modules ]; \
@@ -116,11 +102,35 @@ RUN export PEM=$HOME/conf/server.pem \
-days 3650 \
-subj "/CN=localhost"
+RUN sed -i 's|^#CTL_OVER_HTTP=|CTL_OVER_HTTP=../|' "$HOME/conf/ejabberdctl.cfg"
+
RUN home_root_dir=$(echo $HOME | sed 's|\(.*\)/.*|\1 |') \
&& setcap 'cap_net_bind_service=+ep' $(find $home_root_dir -name beam.smp) \
&& echo -e \
"#!/bin/sh \
\n[ -z \$ERLANG_NODE_ARG ] && export ERLANG_NODE_ARG=ejabberd@localhost \
+ \nexport EMA=\"\$EJABBERD_MACRO_ADMIN\" \
+ \nexport HOST=\"\${EJABBERD_MACRO_HOST:-localhost}\" \
+ \nif [ -n \"\$EMA\" ] \
+ \nthen \
+ \n if [ \"\$EMA\" != \"\${EMA%%@*}\" ] \
+ \n then \
+ \n export USERNAME=\"\${EMA%%@*}\" \
+ \n export HOST=\"\${EMA##*@}\" \
+ \n else \
+ \n export USERNAME=\"\$EMA\" \
+ \n export SHOW_WARNING=\"true\" \
+ \n fi \
+ \nelif [ -n \"\$REGISTER_ADMIN_PASSWORD\" ] \
+ \nthen \
+ \n export USERNAME=\"admin\" \
+ \nelse \
+ \n export USERNAME=\"\$(od -A n -N 8 -t x8 /dev/urandom)\" \
+ \nfi \
+ \nexport EJABBERD_MACRO_ADMIN=\"\$USERNAME@\$HOST\" \
+ \n[ -n \"\$SHOW_WARNING\" ] && echo \"WARNING: The EJABBERD_MACRO_ADMIN environment variable was set to '\$EMA', but it should include the host... I'll overwrite it to become '\$EJABBERD_MACRO_ADMIN'.\" \
+ \n[ -n \"\$CTL_ON_CREATE\" ] && export SEPARATOR=\";\" \
+ \n[ -n \"\$REGISTER_ADMIN_PASSWORD\" ] && export CTL_ON_CREATE=\"register \${EJABBERD_MACRO_ADMIN%%@*} \${EJABBERD_MACRO_ADMIN##*@} \$REGISTER_ADMIN_PASSWORD \$SEPARATOR \$CTL_ON_CREATE\" \
\nexport CONFIG_DIR=/$HOME/conf \
\nexport LOGS_DIR=/$HOME/logs \
\nexport SPOOL_DIR=/$HOME/database \
@@ -137,28 +147,28 @@ RUN home_root_dir=$(echo $HOME | sed 's|\(.*\)/.*|\1 |') \
ARG UID
RUN chown -R $UID:$UID $HOME
+RUN cp /rootfs/$HOME-$VERSION/lib/captcha*.sh usr/local/bin/
+RUN mkdir $HOME/sql \
+ && find /rootfs/$HOME-$VERSION/lib/ -name *.sql -exec cp {} $HOME/sql \; -exec cp {} $HOME/database \;
+
################################################################################
-#' METHOD='direct' - Remove erlang/OTP & rebar3
-FROM docker.io/erlang:${OTP_VSN}-alpine AS runtime-direct
+#' Remove erlang/OTP & rebar3
+FROM docker.io/erlang:${OTP_VSN}-alpine AS runtime
RUN apk del .erlang-rundeps \
&& rm -f $(which rebar3) \
&& find /usr -type d -name 'erlang' -exec rm -rf {} + \
&& find /usr -type l -exec test ! -e {} \; -delete
-################################################################################
-#' METHOD='package' - define runtime base image
-FROM docker.io/alpine:${ALPINE_VSN} AS runtime-package
-
-################################################################################
#' Update alpine, finalize runtime environment
-FROM runtime-${METHOD} AS runtime
COPY --from=ejabberd /tmp/runDeps /tmp/runDeps
RUN apk -U upgrade --available --no-cache \
&& apk add --no-cache \
$(cat /tmp/runDeps) \
so:libcap.so.2 \
so:libtdsodbc.so.0 \
+ curl \
tini \
+ && rm /tmp/runDeps \
&& ln -fs /usr/lib/libtdsodbc.so.0 /usr/lib/libtdsodbc.so
ARG USER
@@ -167,9 +177,13 @@ ARG HOME
RUN addgroup $USER -g $UID \
&& adduser -s /sbin/nologin -D -u $UID -h /$HOME -G $USER $USER
+RUN ln -fs /usr/local/bin/ /opt/ejabberd/bin
+RUN rm -rf /home \
+ && ln -fs /opt /home
+
################################################################################
#' Build together production image
-FROM scratch AS prod
+FROM scratch
ARG USER
ARG HOME
@@ -186,7 +200,7 @@ HEALTHCHECK \
WORKDIR /$HOME
USER $USER
VOLUME ["/$HOME"]
-EXPOSE 1883 4369-4399 5210 5222 5269 5280 5443
+EXPOSE 1880 1883 4369-4399 5210 5222 5269 5280 5443
ENTRYPOINT ["/sbin/tini","--","ejabberdctl"]
CMD ["foreground"]
diff --git a/.github/container/ejabberd.yml.example b/.github/container/ejabberd.yml.example
new file mode 100644
index 000000000..62dff50c9
--- /dev/null
+++ b/.github/container/ejabberd.yml.example
@@ -0,0 +1,278 @@
+###
+### ejabberd configuration file
+###
+### The parameters used in this configuration file are explained at
+###
+### https://docs.ejabberd.im/admin/configuration
+###
+### The configuration file is written in YAML.
+### *******************************************************
+### ******* !!! WARNING !!! *******
+### ******* YAML IS INDENTATION SENSITIVE *******
+### ******* MAKE SURE YOU INDENT SECTIONS CORRECTLY *******
+### *******************************************************
+### Refer to http://en.wikipedia.org/wiki/YAML for the brief description.
+###
+
+define_macro:
+ HOST: localhost
+ ## ADMIN: ... # set by /usr/local/bin/ejabberdctl
+ PORT_C2S: 5222
+ PORT_C2S_TLS: 5223
+ PORT_S2S: 5269
+ PORT_HTTP_TLS: 5443
+ PORT_HTTP: 5280
+ PORT_BROWSER: 1880
+ PORT_STUN: 5478
+ PORT_MQTT: 1883
+ PORT_PROXY65: 7777
+
+hosts:
+ - HOST
+
+loglevel: info
+
+## If you already have certificates, list them here
+# certfiles:
+# - /etc/letsencrypt/live/domain.tld/fullchain.pem
+# - /etc/letsencrypt/live/domain.tld/privkey.pem
+
+ca_file: /opt/ejabberd/conf/cacert.pem
+certfiles:
+ - /opt/ejabberd/conf/server.pem
+
+listen:
+ -
+ port: PORT_C2S
+ ip: "::"
+ module: ejabberd_c2s
+ max_stanza_size: 262144
+ shaper: c2s_shaper
+ access: c2s
+ starttls_required: true
+ -
+ port: PORT_C2S_TLS
+ ip: "::"
+ module: ejabberd_c2s
+ max_stanza_size: 262144
+ shaper: c2s_shaper
+ access: c2s
+ tls: true
+ -
+ port: PORT_S2S
+ ip: "::"
+ module: ejabberd_s2s_in
+ max_stanza_size: 524288
+ shaper: s2s_shaper
+ -
+ port: PORT_HTTP_TLS
+ ip: "::"
+ module: ejabberd_http
+ tls: true
+ request_handlers:
+ /admin: ejabberd_web_admin
+ /api: mod_http_api
+ /bosh: mod_bosh
+ /captcha: ejabberd_captcha
+ /upload: mod_http_upload
+ /ws: ejabberd_http_ws
+ -
+ port: PORT_HTTP
+ ip: "::"
+ module: ejabberd_http
+ request_handlers:
+ /admin: ejabberd_web_admin
+ /.well-known/acme-challenge: ejabberd_acme
+ -
+ port: PORT_BROWSER
+ ip: "::"
+ module: ejabberd_http
+ request_handlers:
+ /: ejabberd_web_admin
+ -
+ port: "unix:../sockets/ctl_over_http.sock"
+ module: ejabberd_http
+ unix_socket:
+ mode: '0600'
+ request_handlers:
+ /ctl: ejabberd_ctl
+ -
+ port: PORT_STUN
+ ip: "::"
+ transport: udp
+ module: ejabberd_stun
+ use_turn: true
+ ## The server's public IPv4 address:
+ # turn_ipv4_address: "203.0.113.3"
+ ## The server's public IPv6 address:
+ # turn_ipv6_address: "2001:db8::3"
+ -
+ port: PORT_MQTT
+ ip: "::"
+ module: mod_mqtt
+ backlog: 1000
+
+s2s_use_starttls: optional
+
+acl:
+ local:
+ user_regexp: ""
+ loopback:
+ ip:
+ - 127.0.0.0/8
+ - ::1/128
+ admin:
+ user:
+ - ADMIN
+
+access_rules:
+ local:
+ allow: local
+ c2s:
+ deny: blocked
+ allow: all
+ announce:
+ allow: admin
+ configure:
+ allow: admin
+ muc_create:
+ allow: local
+ pubsub_createnode:
+ allow: local
+ trusted_network:
+ allow: loopback
+
+api_permissions:
+ "console commands":
+ from: ejabberd_ctl
+ who: all
+ what: "*"
+ "webadmin commands":
+ from: ejabberd_web_admin
+ who: admin
+ what: "*"
+ "admin access":
+ who:
+ access:
+ allow:
+ - acl: loopback
+ - acl: admin
+ oauth:
+ scope: "ejabberd:admin"
+ access:
+ allow:
+ - acl: loopback
+ - acl: admin
+ what:
+ - "*"
+ - "!stop"
+ - "!start"
+ "public commands":
+ who:
+ ip: 127.0.0.1/8
+ what:
+ - status
+ - connected_users_number
+
+shaper:
+ normal:
+ rate: 3000
+ burst_size: 20000
+ fast: 100000
+
+shaper_rules:
+ max_user_sessions: 10
+ max_user_offline_messages:
+ 5000: admin
+ 100: all
+ c2s_shaper:
+ none: admin
+ normal: all
+ s2s_shaper: fast
+
+modules:
+ mod_adhoc: {}
+ mod_admin_extra: {}
+ mod_announce:
+ access: announce
+ mod_avatar: {}
+ mod_blocking: {}
+ mod_bosh: {}
+ mod_caps: {}
+ mod_carboncopy: {}
+ mod_client_state: {}
+ mod_configure: {}
+ mod_disco: {}
+ mod_fail2ban: {}
+ mod_http_api: {}
+ mod_http_upload:
+ put_url: https://@HOST@:5443/upload
+ custom_headers:
+ "Access-Control-Allow-Origin": "https://@HOST@"
+ "Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS"
+ "Access-Control-Allow-Headers": "Content-Type"
+ mod_last: {}
+ mod_mam:
+ ## Mnesia is limited to 2GB, better to use an SQL backend
+ ## For small servers SQLite is a good fit and is very easy
+ ## to configure. Uncomment this when you have SQL configured:
+ ## db_type: sql
+ assume_mam_usage: true
+ default: always
+ mod_mqtt: {}
+ mod_muc:
+ access:
+ - allow
+ access_admin:
+ - allow: admin
+ access_create: muc_create
+ access_persistent: muc_create
+ access_mam:
+ - allow
+ default_room_options:
+ mam: true
+ mod_muc_admin: {}
+ mod_offline:
+ access_max_user_messages: max_user_offline_messages
+ mod_ping: {}
+ mod_privacy: {}
+ mod_private: {}
+ mod_proxy65:
+ access: local
+ max_connections: 5
+ port: PORT_PROXY65
+ mod_pubsub:
+ access_createnode: pubsub_createnode
+ plugins:
+ - flat
+ - pep
+ force_node_config:
+ ## Avoid buggy clients to make their bookmarks public
+ storage:bookmarks:
+ access_model: whitelist
+ mod_push: {}
+ mod_push_keepalive: {}
+ mod_register:
+ ## Only accept registration requests from the "trusted"
+ ## network (see access_rules section above).
+ ## Think twice before enabling registration from any
+ ## address. See the Jabber SPAM Manifesto for details:
+ ## https://github.com/ge0rg/jabber-spam-fighting-manifesto
+ ip_access: trusted_network
+ mod_roster:
+ versioning: true
+ mod_s2s_bidi: {}
+ mod_s2s_dialback: {}
+ mod_shared_roster: {}
+ mod_stream_mgmt:
+ resend_on_timeout: if_offline
+ mod_stun_disco: {}
+ mod_vcard: {}
+ mod_vcard_xupdate: {}
+ mod_version:
+ show_os: false
+
+### Local Variables:
+### mode: yaml
+### End:
+### vim: set filetype=yaml tabstop=8
diff --git a/.github/container/ejabberdctl.template b/.github/container/ejabberdctl.template
index a27a14587..2353040d5 100755
--- a/.github/container/ejabberdctl.template
+++ b/.github/container/ejabberdctl.template
@@ -305,6 +305,8 @@ stop_epmd()
# if all ok, ensure runtime directory exists and make it current directory
check_start()
{
+ ECSIMAGE_DBPATH=$HOME/database/$ERLANG_NODE
+ [ ! -d "$ECSIMAGE_DBPATH" ] && ln -s $HOME/database $HOME/database/$ERLANG_NODE
[ -n "$ERL_DIST_PORT" ] && return
"$EPMD" -names 2>/dev/null | grep -q " ${ERLANG_NODE%@*} " && {
pgrep -f "$ERLANG_NODE" >/dev/null && {
@@ -377,6 +379,54 @@ wait_status()
[ $timeout -gt 0 ]
}
+exec_other_command()
+{
+ if [ -z "$CTL_OVER_HTTP" ] || [ ! -S "$CTL_OVER_HTTP" ] \
+ || [ ! -x "$(command -v curl)" ] || [ -z "$1" ] || [ "$1" = "help" ] \
+ || [ "$1" = "mnesia_info_ctl" ]|| [ "$1" = "print_sql_schema" ] ; then
+ run_erl "$(uid ctl)" -hidden -noinput \
+ -eval 'net_kernel:connect_node('"'$ERLANG_NODE'"')' \
+ -s ejabberd_ctl \
+ -extra "$ERLANG_NODE" $NO_TIMEOUT "$@"
+ result=$?
+ case $result in
+ 3) help;;
+ *) :;;
+ esac
+ exit $result
+ else
+ exec_ctl_over_http_socket "$@"
+ fi
+}
+
+exec_ctl_over_http_socket()
+{
+ COMMAND=${1}
+ CARGS=""
+ while [ $# -gt 0 ]; do
+ [ -z "$CARGS" ] && CARGS="[" || CARGS="${CARGS}, "
+ CARGS="${CARGS}\"$1\""
+ shift
+ done
+ CARGS="${CARGS}]"
+ TEMPHEADERS=temp-headers.log
+ curl \
+ --unix-socket ${CTL_OVER_HTTP} \
+ --header "Content-Type: application/json" \
+ --header "Accept: application/json" \
+ --data "${CARGS}" \
+ --dump-header ${TEMPHEADERS} \
+ --no-progress-meter \
+ "http://localhost/ctl/${COMMAND}"
+ result=$(sed -n 's/.*status-code: \([0-9]*\).*/\1/p' < $TEMPHEADERS)
+ rm ${TEMPHEADERS}
+ case $result in
+ 2|3) exec_other_command help ${COMMAND};;
+ *) :;;
+ esac
+ exit $result
+}
+
# ensure we can change current directory to SPOOL_DIR
[ -f "$SPOOL_DIR/schema.DAT" ] || FIRST_RUN=true
[ -d "$SPOOL_DIR" ] || run_cmd mkdir -p "$SPOOL_DIR"
@@ -452,15 +502,6 @@ case $1 in
;;
*)
set_dist_client
- run_erl "$(uid ctl)" -hidden -noinput \
- -eval 'net_kernel:connect_node('"'$ERLANG_NODE'"')' \
- -s ejabberd_ctl \
- -extra "$ERLANG_NODE" $NO_TIMEOUT "$@"
- result=$?
- case $result in
- 2|3) help;;
- *) :;;
- esac
- exit $result
+ exec_other_command "$@"
;;
esac
diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml
index 7940c9b68..567a2c845 100644
--- a/.github/workflows/container.yml
+++ b/.github/workflows/container.yml
@@ -1,8 +1,6 @@
name: Container
on:
- schedule:
- - cron: '22 2 */6 * *' # every 6 days to avoid gha cache being evicted
push:
paths-ignore:
- '.devcontainer/**'
@@ -28,52 +26,6 @@ jobs:
with:
fetch-depth: 0
- - name: Cache build directory
- uses: actions/cache@v4
- with:
- path: ~/build/
- key: ${{runner.os}}-ctr-ct-ng-1.27.0
-
- - name: Get erlang/OTP version for bootstrapping
- run: |
- echo "OTP_VSN=$(awk '/^otp_vsn=/ {{gsub(/[^0-9.rc-]/, ""); print}}' tools/make-binaries)" >> $GITHUB_ENV
- echo "ELIXIR_VSN=$(awk '/^elixir_vsn=/ {{gsub(/[^0-9.]/, ""); print}}' tools/make-binaries)" >> $GITHUB_ENV
-
- - name: Install prerequisites
- run: |
- sudo apt-get -qq update
- sudo apt-get -qq install makeself
- # https://github.com/crosstool-ng/crosstool-ng/blob/master/testing/docker/ubuntu21.10/Dockerfile
- sudo apt-get -qq install build-essential autoconf bison flex gawk
- sudo apt-get -qq install help2man libncurses5-dev libtool libtool-bin
- sudo apt-get -qq install python3-dev texinfo unzip
-
- - name: Install erlang/OTP
- uses: erlef/setup-beam@v1
- with:
- otp-version: ${{ env.OTP_VSN }}
- elixir-version: ${{ env.ELIXIR_VSN }}
- version-type: strict
-
- - name: Remove Elixir Matchers
- run: |
- echo "::remove-matcher owner=elixir-mixCompileWarning::"
- echo "::remove-matcher owner=elixir-credoOutputDefault::"
- echo "::remove-matcher owner=elixir-mixCompileError::"
- echo "::remove-matcher owner=elixir-mixTestFailure::"
- echo "::remove-matcher owner=elixir-dialyzerOutputDefault::"
-
- - name: Build musl-libc based binary archives
- run: |
- sed -i "s|targets='.*'|targets='x86_64-linux-musl aarch64-linux-musl'|" tools/make-binaries
- mv .github/container/ejabberdctl.template .
- CHECK_DEPS=false tools/make-binaries
-
- - name: Collect packages
- run: |
- mkdir tarballs
- mv ejabberd-*.tar.gz tarballs
-
- name: Checkout ejabberd-contrib
uses: actions/checkout@v4
with:
@@ -111,7 +63,6 @@ jobs:
uses: docker/build-push-action@v6
with:
build-args: |
- METHOD=package
VERSION=${{ steps.gitdescribe.outputs.ver }}
cache-from: type=gha
cache-to: type=gha,mode=max
diff --git a/CONTAINER.md b/CONTAINER.md
index 95115a6c2..b8a483e07 100644
--- a/CONTAINER.md
+++ b/CONTAINER.md
@@ -1,10 +1,10 @@
[](https://github.com/processone/ejabberd/tags)
-[](https://github.com/processone/ejabberd/pkgs/container/ejabberd)
+[](https://github.com/processone/ejabberd/pkgs/container/ejabberd)
[](https://hub.docker.com/r/ejabberd/ecs/)
-`ejabberd` Container Image
-==========================
+ejabberd Container Images
+=========================
[ejabberd][home] is an open-source,
robust, scalable and extensible realtime platform built using [Erlang/OTP][erlang],
@@ -16,26 +16,29 @@ that includes [XMPP][xmpp] Server, [MQTT][mqtt] Broker and [SIP][sip] Service.
[mqtt]: https://mqtt.org/
[sip]: https://en.wikipedia.org/wiki/Session_Initiation_Protocol
-This document explains how to use the `ejabberd` container image available in
-[ghcr.io/processone/ejabberd](https://github.com/processone/ejabberd/pkgs/container/ejabberd),
-built using the files in `.github/container/`.
-This image is based in Alpine 3.19, includes Erlang/OTP 27.2 and Elixir 1.18.1.
+This page documents those container images ([images comparison](#images-comparison)):
-Alternatively, there is also the `ecs` container image available in
-[docker.io/ejabberd/ecs](https://hub.docker.com/r/ejabberd/ecs/),
-built using the
-[docker-ejabberd/ecs](https://github.com/processone/docker-ejabberd/tree/master/ecs)
-repository.
-Check the [differences between `ejabberd` and `ecs` images](https://github.com/processone/docker-ejabberd/blob/master/ecs/HUB-README.md#alternative-image-in-github).
+- [](https://github.com/processone/ejabberd/pkgs/container/ejabberd)
+ published in [ghcr.io/processone/ejabberd](https://github.com/processone/ejabberd/pkgs/container/ejabberd),
+ built using [ejabberd](https://github.com/processone/ejabberd/tree/master/.github/container) repository,
+ both for stable ejabberd releases and the `master` branch, in x64 and arm64 architectures.
-If you are using a Windows operating system, check the tutorials mentioned in
-[ejabberd Docs > Docker Image](https://docs.ejabberd.im/admin/install/container/#ejabberd-container-image).
+- [](https://hub.docker.com/r/ejabberd/ecs/)
+ published in [docker.io/ejabberd/ecs](https://hub.docker.com/r/ejabberd/ecs/),
+ built using [docker-ejabberd/ecs](https://github.com/processone/docker-ejabberd/tree/master/ecs) repository
+ for ejabberd stable releases in x64 architectures.
+
+For Microsoft Windows, see
+[Docker Desktop for Windows 10](https://www.process-one.net/blog/install-ejabberd-on-windows-10-using-docker-desktop/),
+and [Docker Toolbox for Windows 7](https://www.process-one.net/blog/install-ejabberd-on-windows-7-using-docker-toolbox/).
+
+For Kubernetes Helm, see [help-ejabberd](https://github.com/sando38/helm-ejabberd).
Start ejabberd
--------------
-### With default configuration
+### daemon
Start ejabberd in a new container:
@@ -46,22 +49,28 @@ docker run --name ejabberd -d -p 5222:5222 ghcr.io/processone/ejabberd
That runs the container as a daemon,
using ejabberd default configuration file and XMPP domain `localhost`.
-Stop the running container:
-
-```bash
-docker stop ejabberd
-```
-
Restart the stopped ejabberd container:
```bash
docker restart ejabberd
```
+Stop the running container:
-### Start with Erlang console attached
+```bash
+docker stop ejabberd
+```
-Start ejabberd with an Erlang console attached using the `live` command:
+Remove the ejabberd container:
+
+```bash
+docker rm ejabberd
+```
+
+
+### with Erlang console
+
+Start ejabberd with an interactive Erlang console attached using the `live` command:
```bash
docker run --name ejabberd -it -p 5222:5222 ghcr.io/processone/ejabberd live
@@ -70,48 +79,67 @@ docker run --name ejabberd -it -p 5222:5222 ghcr.io/processone/ejabberd live
That uses the default configuration file and XMPP domain `localhost`.
-### Start with your configuration and database
+### with your data
Pass a configuration file as a volume
and share the local directory to store database:
```bash
-mkdir database
-chown ejabberd database
+mkdir conf && cp ejabberd.yml.example conf/ejabberd.yml
-cp ejabberd.yml.example ejabberd.yml
+mkdir database && chown ejabberd database
docker run --name ejabberd -it \
- -v $(pwd)/ejabberd.yml:/opt/ejabberd/conf/ejabberd.yml \
+ -v $(pwd)/conf/ejabberd.yml:/opt/ejabberd/conf/ejabberd.yml \
-v $(pwd)/database:/opt/ejabberd/database \
-p 5222:5222 ghcr.io/processone/ejabberd live
```
-Notice that ejabberd runs in the container with an account named `ejabberd`,
+Notice that ejabberd runs in the container with an account named `ejabberd`
+with UID 9000 and group `ejabberd` with GID 9000,
and the volumes you mount must grant proper rights to that account.
Next steps
----------
-### Register the administrator account
+### Register admin account
-The default ejabberd configuration does not grant admin privileges
-to any account,
-you may want to register a new account in ejabberd
-and grant it admin rights.
+#### [](https://github.com/processone/ejabberd/pkgs/container/ejabberd) [:orange_circle:](#images-comparison)
-Register an account using the `ejabberdctl` script:
+If you set the `REGISTER_ADMIN_PASSWORD` environment variable,
+an account is automatically registered with that password,
+and admin privileges are granted to it.
+The account created depends on what variables you have set:
+
+- `EJABBERD_MACRO_ADMIN=juliet@example.org` -> `juliet@example.org`
+- `EJABBERD_MACRO_HOST=example.org` -> `admin@example.org`
+- None of those variables are set -> `admin@localhost`
+
+The account registration is shown in the container log:
+
+```
+:> ejabberdctl register admin example.org somePassw0rd
+User admin@example.org successfully registered
+```
+
+Alternatively, you can register the account manually yourself
+and edit `conf/ejabberd.yml` and add the ACL as explained in
+[ejabberd Docs: Administration Account](https://docs.ejabberd.im/admin/install/next-steps/#administration-account).
+
+---
+
+#### [](https://hub.docker.com/r/ejabberd/ecs/)
+
+The default ejabberd configuration has already granted admin privilege
+to an account that would be called `admin@localhost`,
+so you just need to register it, for example:
```bash
docker exec -it ejabberd ejabberdctl register admin localhost passw0rd
```
-Then edit conf/ejabberd.yml and add the ACL as explained in
-[ejabberd Docs: Administration Account](https://docs.ejabberd.im/admin/install/next-steps/#administration-account)
-
-
-### Check ejabberd log files
+### Check ejabberd log
Check the content of the log files inside the container,
even if you do not put it on a shared persistent drive:
@@ -121,7 +149,7 @@ docker exec -it ejabberd tail -f logs/ejabberd.log
```
-### Inspect the container files
+### Inspect container files
The container uses Alpine Linux. Start a shell inside the container:
@@ -130,7 +158,7 @@ docker exec -it ejabberd sh
```
-### Open ejabberd debug console
+### Open debug console
Open an interactive debug Erlang console attached to a running ejabberd in a running container:
@@ -155,7 +183,7 @@ docker exec -it ejabberd vi conf/ejabberd.yml
and add this option:
```yaml
-captcha_cmd: /opt/ejabberd-22.04/lib/captcha.sh
+captcha_cmd: "$HOME/bin/captcha.sh"
```
Finally, reload the configuration file or restart the container:
@@ -176,20 +204,22 @@ For more details about CAPTCHA options, please check the
documentation section.
-Advanced Container Configuration
---------------------------------
+Advanced
+--------
### Ports
-This container image exposes the ports:
+The container image exposes several ports
+(check also [Docs: Firewall Settings](https://docs.ejabberd.im/admin/guide/security/#firewall-settings)):
- `5222`: The default port for XMPP clients.
- `5269`: For XMPP federation. Only needed if you want to communicate with users on other servers.
-- `5280`: For admin interface.
+- `5280`: For admin interface (URL is `admin/`).
+- `1880`: For admin interface (URL is `/`, useful for [podman-desktop](https://podman-desktop.io/) and [docker-desktop](https://www.docker.com/products/docker-desktop/)) [:orange_circle:](#images-comparison)
- `5443`: With encryption, used for admin interface, API, CAPTCHA, OAuth, Websockets and XMPP BOSH.
- `1883`: Used for MQTT
- `4369-4399`: EPMD and Erlang connectivity, used for `ejabberdctl` and clustering
-- `5210`: Erlang connectivity when `ERL_DIST_PORT` is set, alternative to EPMD
+- `5210`: Erlang connectivity when `ERL_DIST_PORT` is set, alternative to EPMD [:orange_circle:](#images-comparison)
### Volumes
@@ -206,11 +236,26 @@ You should back up or export the content of the directory to persistent storage
- `/opt/ejabberd/logs/`: Directory containing log files
- `/opt/ejabberd/upload/`: Directory containing uploaded files. This should also be backed up.
-All these files are owned by `ejabberd` user inside the container.
+All these files are owned by an account named `ejabberd` with group `ejabberd` in the container.
+Its corresponding `UID:GID` is `9000:9000`.
+If you prefer bind mounts instead of volumes, then
+you need to map this to valid `UID:GID` on your host to get read/write access on
+mounted directories.
-It's possible to install additional ejabberd modules using volumes,
-[this comment](https://github.com/processone/docker-ejabberd/issues/81#issuecomment-1036115146)
-explains how to install an additional module using docker-compose.
+If using Docker, try:
+```bash
+mkdir database
+sudo chown 9000:9000 database
+```
+
+If using Podman, try:
+```bash
+mkdir database
+podman unshare chown 9000:9000 database
+```
+
+It's possible to install additional ejabberd modules using volumes, check
+[this Docs tutorial](http://docs.ejabberd.im/developer/extending-ejabberd/modules/#your-module-in-ejabberd-modules-with-ejabberd-container).
### Commands on start
@@ -234,10 +279,10 @@ Example usage (or check the [full example](#customized-example)):
```
-### Macros in environment
+### Macros in environment [:high_brightness:](#images-comparison)
ejabberd reads `EJABBERD_MACRO_*` environment variables
-and uses them to define the `*`
+and uses them to define the corresponding
[macros](https://docs.ejabberd.im/admin/configuration/file-format/#macros-in-configuration-file),
overwriting the corresponding macro definition if it was set in the configuration file.
This is supported since ejabberd 24.12.
@@ -247,18 +292,61 @@ For example, if you configure this in `ejabberd.yml`:
```yaml
acl:
admin:
- user: ADMINJID
+ user: ADMIN
```
now you can define the admin account JID using an environment variable:
```yaml
environment:
- - EJABBERD_MACRO_ADMINJID=admin@localhost
+ - EJABBERD_MACRO_ADMIN=admin@localhost
```
Check the [full example](#customized-example) for other example.
+### ejabberdapi
+
+When the container is running (and thus ejabberd), you can exec commands inside the container
+using `ejabberdctl` or any other of the available interfaces, see
+[Understanding ejabberd "commands"](https://docs.ejabberd.im/developer/ejabberd-api/#understanding-ejabberd-commands)
+
+Additionally, the container image includes the `ejabberdapi` executable.
+Please check the [ejabberd-api homepage](https://github.com/processone/ejabberd-api)
+for configuration and usage details.
+
+For example, if you configure ejabberd like this:
+```yaml
+listen:
+ -
+ port: 5282
+ module: ejabberd_http
+ request_handlers:
+ "/api": mod_http_api
+
+acl:
+ loopback:
+ ip:
+ - 127.0.0.0/8
+ - ::1/128
+ - ::FFFF:127.0.0.1/128
+
+api_permissions:
+ "admin access":
+ who:
+ access:
+ allow:
+ acl: loopback
+ what:
+ - "register"
+```
+
+Then you could register new accounts with this query:
+
+```bash
+docker exec -it ejabberd ejabberdapi register --endpoint=http://127.0.0.1:5282/ --jid=admin@localhost --password=passw0rd
+```
+
+
### Clustering
When setting several containers to form a
@@ -273,6 +361,8 @@ For this you can either:
- edit `conf/ejabberdctl.cfg` and set variables `ERLANG_NODE` and `ERLANG_COOKIE`
- set the environment variables `ERLANG_NODE_ARG` and `ERLANG_COOKIE`
+---
+
Example to connect a local `ejabberdctl` to a containerized ejabberd:
1. When creating the container, export port 5210, and set `ERLANG_COOKIE`:
@@ -282,7 +372,7 @@ Example to connect a local `ejabberdctl` to a containerized ejabberd:
-p 5210:5210 -p 5222:5222 \
ghcr.io/processone/ejabberd
```
-2. Set `ERL_DIST_PORT=5210` in ejabberdctl.cfg of container and local ejabberd
+2. Set `ERL_DIST_PORT=5210` in `ejabberdctl.cfg` of container and local ejabberd
3. Restart the container
4. Now use `ejabberdctl` in your local ejabberd deployment
@@ -298,19 +388,175 @@ Example using environment variables (see full example [docker-compose.yml](https
- ERLANG_COOKIE=dummycookie123
```
+---
-Build a Container Image
------------------------
+Once you have the ejabberd nodes properly set and running,
+you can tell the secondary nodes to join the master node using the
+[`join_cluster`](https://docs.ejabberd.im/developer/ejabberd-api/admin-api/#join-cluster)
+API call.
-This container image includes ejabberd as a standalone OTP release built using Elixir.
-That OTP release is configured with:
+Example using environment variables (see the full
+[`docker-compose.yml` clustering example](#clustering-example)):
+```yaml
+environment:
+ - ERLANG_NODE_ARG=ejabberd@replica
+ - ERLANG_COOKIE=dummycookie123
+ - CTL_ON_CREATE=join_cluster ejabberd@main
+```
+
+### Change Mnesia Node Name
+
+To use the same Mnesia database in a container with a different hostname,
+it is necessary to change the old hostname stored in Mnesia.
+
+This section is equivalent to the ejabberd Documentation
+[Change Computer Hostname](https://docs.ejabberd.im/admin/guide/managing/#change-computer-hostname),
+but particularized to containers that use this
+ecs container image from ejabberd 23.01 or older.
+
+#### Setup Old Container
+
+Let's assume a container running ejabberd 23.01 (or older) from
+this ecs container image, with the database directory binded
+and one registered account.
+This can be produced with:
+```bash
+OLDCONTAINER=ejaold
+NEWCONTAINER=ejanew
+
+mkdir database
+sudo chown 9000:9000 database
+docker run -d --name $OLDCONTAINER -p 5222:5222 \
+ -v $(pwd)/database:/opt/ejabberd/database \
+ ghcr.io/processone/ejabberd:23.01
+docker exec -it $OLDCONTAINER ejabberdctl started
+docker exec -it $OLDCONTAINER ejabberdctl register user1 localhost somepass
+docker exec -it $OLDCONTAINER ejabberdctl registered_users localhost
+```
+
+Methods to know the Erlang node name:
+```bash
+ls database/ | grep ejabberd@
+docker exec -it $OLDCONTAINER ejabberdctl status
+docker exec -it $OLDCONTAINER grep "started in the node" logs/ejabberd.log
+```
+
+#### Change Mnesia Node
+
+First of all let's store the Erlang node names and paths in variables.
+In this example they would be:
+```bash
+OLDCONTAINER=ejaold
+NEWCONTAINER=ejanew
+OLDNODE=ejabberd@95145ddee27c
+NEWNODE=ejabberd@localhost
+OLDFILE=/opt/ejabberd/database/old.backup
+NEWFILE=/opt/ejabberd/database/new.backup
+```
+
+1. Start your old container that can still read the Mnesia database correctly.
+If you have the Mnesia spool files,
+but don't have access to the old container anymore, go to
+[Create Temporary Container](#create-temporary-container)
+and later come back here.
+
+2. Generate a backup file and check it was created:
+```bash
+docker exec -it $OLDCONTAINER ejabberdctl backup $OLDFILE
+ls -l database/*.backup
+```
+
+3. Stop ejabberd:
+```bash
+docker stop $OLDCONTAINER
+```
+
+4. Create the new container. For example:
+```bash
+docker run \
+ --name $NEWCONTAINER \
+ -d \
+ -p 5222:5222 \
+ -v $(pwd)/database:/opt/ejabberd/database \
+ ghcr.io/processone/ejabberd:latest
+```
+
+5. Convert the backup file to new node name:
+```bash
+docker exec -it $NEWCONTAINER ejabberdctl mnesia_change_nodename $OLDNODE $NEWNODE $OLDFILE $NEWFILE
+```
+
+6. Install the backup file as a fallback:
+```bash
+docker exec -it $NEWCONTAINER ejabberdctl install_fallback $NEWFILE
+```
+
+7. Restart the container:
+```bash
+docker restart $NEWCONTAINER
+```
+
+8. Check that the information of the old database is available.
+In this example, it should show that the account `user1` is registered:
+```bash
+docker exec -it $NEWCONTAINER ejabberdctl registered_users localhost
+```
+
+9. When the new container is working perfectly with the converted Mnesia database,
+you may want to remove the unneeded files:
+the old container, the old Mnesia spool files, and the backup files.
+
+#### Create Temporary Container
+
+In case the old container that used the Mnesia database is not available anymore,
+a temporary container can be created just to read the Mnesia database
+and make a backup of it, as explained in the previous section.
+
+This method uses `--hostname` command line argument for docker,
+and `ERLANG_NODE_ARG` environment variable for ejabberd.
+Their values must be the hostname of your old container
+and the Erlang node name of your old ejabberd node.
+To know the Erlang node name please check
+[Setup Old Container](#setup-old-container).
+
+Command line example:
+```bash
+OLDHOST=${OLDNODE#*@}
+docker run \
+ -d \
+ --name $OLDCONTAINER \
+ --hostname $OLDHOST \
+ -p 5222:5222 \
+ -v $(pwd)/database:/opt/ejabberd/database \
+ -e ERLANG_NODE_ARG=$OLDNODE \
+ ghcr.io/processone/ejabberd:latest
+```
+
+Check the old database content is available:
+```bash
+docker exec -it $OLDCONTAINER ejabberdctl registered_users localhost
+```
+
+Now that you have ejabberd running with access to the Mnesia database,
+you can continue with step 2 of previous section
+[Change Mnesia Node](#change-mnesia-node).
+
+
+Build Container Image
+----------------
+
+The container image includes ejabberd as a standalone OTP release built using Elixir.
+
+### Build `ejabberd` [](https://github.com/processone/ejabberd/pkgs/container/ejabberd)
+
+The ejabberd Erlang/OTP release is configured with:
- `mix.exs`: Customize ejabberd release
- `vars.config`: ejabberd compilation configuration options
- `config/runtime.exs`: Customize ejabberd paths
- `ejabberd.yml.template`: ejabberd default config file
-### Direct build
+#### Direct build
Build ejabberd Community Server container image from ejabberd master git repository:
@@ -321,7 +567,7 @@ docker buildx build \
.
```
-### Podman build
+#### Podman build
To build the image using Podman, please notice:
@@ -346,30 +592,27 @@ podman stop eja1
podman run --name eja1 -it -e EJABBERD_BYPASS_WARNINGS=true -p 5222:5222 localhost/ejabberd live
```
-### Package build for `arm64`
+### Build `ecs` [](https://hub.docker.com/r/ejabberd/ecs/)
-By default, `.github/container/Dockerfile` builds this container by directly compiling ejabberd,
-it is a fast and direct method.
-However, a problem with QEMU prevents building the container in QEMU using Erlang/OTP 25
-for the `arm64` architecture.
+The ejabberd Erlang/OTP release is configured with:
-Providing `--build-arg METHOD=package` is an alternate method to build the container
-used by the Github Actions workflow that provides `amd64` and `arm64` container images.
-It first builds an ejabberd binary package, and later installs it in the image.
-That method avoids using QEMU, so it can build `arm64` container images, but is extremely
-slow the first time it's used, and consequently not recommended for general use.
+- `rel/config.exs`: Customize ejabberd release
+- `rel/dev.exs`: ejabberd environment configuration for development release
+- `rel/prod.exs`: ejabberd environment configuration for production release
+- `vars.config`: ejabberd compilation configuration options
+- `conf/ejabberd.yml`: ejabberd default config file
-In this case, to build the ejabberd container image for arm64 architecture:
+Build ejabberd Community Server base image from ejabberd master on Github:
```bash
-docker buildx build \
- --build-arg METHOD=package \
- --platform linux/arm64 \
- -t personal/ejabberd:$VERSION \
- -f .github/container/Dockerfile \
- .
+docker build -t personal/ejabberd .
```
+Build ejabberd Community Server base image for a given ejabberd version:
+
+```bash
+./build.sh 18.03
+```
Composer Examples
-----------------
@@ -430,26 +673,21 @@ stores the mnesia database in a local path,
registers an account when it's created,
and checks the number of registered accounts every time it's started.
-Download or copy the ejabberd configuration file:
+Prepare an ejabberd configuration file:
```bash
-wget https://raw.githubusercontent.com/processone/ejabberd/master/ejabberd.yml.example
-mv ejabberd.yml.example ejabberd.yml
-```
-
-Use a macro in `ejabberd.yml` to set the served vhost, with `localhost` as default value:
-```yaml
-define_macro:
- XMPPHOST: localhost
-
-hosts:
- - XMPPHOST
+mkdir conf && cp ejabberd.yml.example conf/ejabberd.yml
```
Create the database directory and allow the container access to it:
-```bash
-mkdir database
-sudo chown 9000:9000 database
-```
+
+- Docker:
+ ```bash
+ mkdir database && sudo chown 9000:9000 database
+ ```
+- Podman:
+ ```bash
+ mkdir database && podman unshare chown 9000:9000 database
+ ```
If using Docker, write this `docker-compose.yml` file
and start it with `docker-compose up`:
@@ -463,8 +701,9 @@ services:
image: ghcr.io/processone/ejabberd
container_name: ejabberd
environment:
- - EJABBERD_MACRO_XMPPHOST=example.com
- - CTL_ON_CREATE=register admin example.com asd
+ - EJABBERD_MACRO_HOST=example.com
+ - EJABBERD_MACRO_ADMIN=admin@example.com
+ - REGISTER_ADMIN_PASSWORD=somePassw0rd
- CTL_ON_START=registered_users example.com ;
status
ports:
@@ -473,7 +712,7 @@ services:
- "5280:5280"
- "5443:5443"
volumes:
- - ./ejabberd.yml:/opt/ejabberd/conf/ejabberd.yml:ro
+ - ./conf/ejabberd.yml:/opt/ejabberd/conf/ejabberd.yml:ro
- ./database:/opt/ejabberd/database
```
@@ -494,8 +733,12 @@ spec:
- name: ejabberd
image: ghcr.io/processone/ejabberd
env:
- - name: CTL_ON_CREATE
- value: register admin example.com asd
+ - name: EJABBERD_MACRO_HOST
+ value: example.com
+ - name: EJABBERD_MACRO_ADMIN
+ value: admin@example.com
+ - name: REGISTER_ADMIN_PASSWORD
+ value: somePassw0rd
- name: CTL_ON_START
value: registered_users example.com ;
status
@@ -518,7 +761,7 @@ spec:
volumes:
- name: config
hostPath:
- path: ./ejabberd.yml
+ path: ./conf/ejabberd.yml
type: File
- name: db
hostPath:
@@ -551,11 +794,17 @@ services:
main:
image: ghcr.io/processone/ejabberd
- container_name: ejabberd
+ container_name: main
environment:
- ERLANG_NODE_ARG=ejabberd@main
- ERLANG_COOKIE=dummycookie123
- CTL_ON_CREATE=! register admin localhost asd
+ healthcheck:
+ test: netstat -nl | grep -q 5222
+ start_period: 5s
+ interval: 5s
+ timeout: 5s
+ retries: 120
replica:
image: ghcr.io/processone/ejabberd
@@ -654,92 +903,41 @@ spec:
```
-Your configuration file should use those macros to allow each ejabberd node
-use different listening port numbers:
-```diff
-diff --git a/ejabberd.yml.example b/ejabberd.yml.example
-index 39e423a64..6e875b48f 100644
---- a/ejabberd.yml.example
-+++ b/ejabberd.yml.example
-@@ -24,9 +24,19 @@ loglevel: info
- # - /etc/letsencrypt/live/domain.tld/fullchain.pem
- # - /etc/letsencrypt/live/domain.tld/privkey.pem
-
-+define_macro:
-+ PORT_C2S: 5222
-+ PORT_C2S_TLS: 5223
-+ PORT_S2S: 5269
-+ PORT_HTTP_TLS: 5443
-+ PORT_HTTP: 5280
-+ PORT_STUN: 5478
-+ PORT_MQTT: 1883
-+ PORT_PROXY65: 7777
-+
- listen:
- -
-- port: 5222
-+ port: PORT_C2S
- ip: "::"
- module: ejabberd_c2s
- max_stanza_size: 262144
-@@ -34,7 +44,7 @@ listen:
- access: c2s
- starttls_required: true
- -
-- port: 5223
-+ port: PORT_C2S_TLS
- ip: "::"
- module: ejabberd_c2s
- max_stanza_size: 262144
-@@ -42,13 +52,13 @@ listen:
- access: c2s
- tls: true
- -
-- port: 5269
-+ port: PORT_S2S
- ip: "::"
- module: ejabberd_s2s_in
- max_stanza_size: 524288
- shaper: s2s_shaper
- -
-- port: 5443
-+ port: PORT_HTTP_TLS
- ip: "::"
- module: ejabberd_http
- tls: true
-@@ -60,14 +70,14 @@ listen:
- /upload: mod_http_upload
- /ws: ejabberd_http_ws
- -
-- port: 5280
-+ port: PORT_HTTP
- ip: "::"
- module: ejabberd_http
- request_handlers:
- /admin: ejabberd_web_admin
- /.well-known/acme-challenge: ejabberd_acme
- -
-- port: 5478
-+ port: PORT_STUN
- ip: "::"
- transport: udp
- module: ejabberd_stun
-@@ -77,7 +87,7 @@ listen:
- ## The server's public IPv6 address:
- # turn_ipv6_address: "2001:db8::3"
- -
-- port: 1883
-+ port: PORT_MQTT
- ip: "::"
- module: mod_mqtt
- backlog: 1000
-@@ -207,6 +217,7 @@ modules:
- mod_proxy65:
- access: local
- max_connections: 5
-+ port: PORT_PROXY65
- mod_pubsub:
- access_createnode: pubsub_createnode
- plugins:
-```
+Images Comparison
+-----------------
+
+Let's summarize the differences between both container images. Legend:
+
+- :sparkle: is the recommended alternative
+- :orange_circle: added in the latest release (ejabberd 25.xx)
+- :high_brightness: added in the previous release (ejabberd 24.12)
+- :low_brightness: added in the pre-previous release (ejabberd 24.10)
+
+| | [](https://github.com/processone/ejabberd/pkgs/container/ejabberd) | [](https://hub.docker.com/r/ejabberd/ecs/) |
+|:----------------------|:------------------|:-----------------------|
+| Source code | [ejabberd/.github/container](https://github.com/processone/ejabberd/tree/master/.github/container) | [docker-ejabberd/ecs](https://github.com/processone/docker-ejabberd/tree/master/ecs) |
+| Generated by | [container.yml](https://github.com/processone/ejabberd/blob/master/.github/workflows/container.yml) | [tests.yml](https://github.com/processone/docker-ejabberd/blob/master/.github/workflows/tests.yml) |
+| Built for | stable releases
`master` branch | stable releases
[`master` branch zip](https://github.com/processone/docker-ejabberd/actions/workflows/tests.yml) |
+| Architectures | `linux/amd64`
`linux/arm64` | `linux/amd64` |
+| Software | Erlang/OTP 27.2-alpine
Elixir 1.18.1 | Alpine 3.19
Erlang/OTP 26.2
Elixir 1.15.7 |
+| Published in | [ghcr.io/processone/ejabberd](https://github.com/processone/ejabberd/pkgs/container/ejabberd) | [docker.io/ejabberd/ecs](https://hub.docker.com/r/ejabberd/ecs/)
[ghcr.io/processone/ecs](https://github.com/processone/docker-ejabberd/pkgs/container/ecs) |
+| :black_square_button: **Additional content** |
+| [ejabberd-contrib](https://docs.ejabberd.im/admin/guide/modules/#ejabberd-contrib) | included | not included |
+| [ejabberdapi](#ejabberdapi) | included :orange_circle: | included |
+| :black_square_button: **Ports** |
+| [1880](#ports) for WebAdmin | yes :orange_circle: | yes :orange_circle: |
+| [5210](#ports) for `ERL_DIST_PORT` | supported | supported :orange_circle: |
+| :black_square_button: **Paths** |
+| `$HOME` | `/opt/ejabberd/` | `/home/ejabberd/` |
+| User data | `$HOME` :sparkle:
`/home/ejabberd/` :orange_circle: | `$HOME`
`/opt/ejabberd/` :sparkle: :low_brightness: |
+| `ejabberdctl` | `ejabberdctl` :sparkle:
`bin/ejabberdctl` :orange_circle: | `bin/ejabberdctl`
`ejabberdctl` :sparkle: :low_brightness: |
+| [`captcha.sh`](#captcha) | `$HOME/bin/captcha.sh` :orange_circle: | `$HOME/bin/captcha.sh` :orange_circle: |
+| `*.sql` files | `$HOME/sql/*.sql` :sparkle: :orange_circle:
`$HOME/database/*.sql` :orange_circle: | `$HOME/database/*.sql`
`$HOME/sql/*.sql` :sparkle: :orange_circle: |
+| Mnesia spool files | `$HOME/database/` :sparkle:
`$HOME/database/NODENAME/` :orange_circle: | `$HOME/database/NODENAME/`
`$HOME/database/` :sparkle: :orange_circle: |
+| :black_square_button: **Variables** |
+| [`EJABBERD_MACRO_*`](#macros-in-environment) | supported :high_brightness: | supported :high_brightness: |
+| Macros used in `ejabberd.yml` | yes :orange_circle: | yes :orange_circle: |
+| [`EJABBERD_MACRO_ADMIN`](#register-admin-account) | Grant admin rights :orange_circle:
(default `admin@localhost`)
| Hardcoded `admin@localhost` |
+| [`REGISTER_ADMIN_PASSWORD`](#register-admin-account) | Register admin account :orange_circle: | unsupported |
+| `CTL_OVER_HTTP` | enabled :orange_circle: | unsupported |
diff --git a/ejabberdctl.cfg.example b/ejabberdctl.cfg.example
index 6887fb4cc..88f99cd78 100644
--- a/ejabberdctl.cfg.example
+++ b/ejabberdctl.cfg.example
@@ -198,6 +198,17 @@
#
#CONTRIB_MODULES_CONF_DIR=/etc/ejabberd/modules
+#.
+#' CTL_OVER_HTTP: Path to ejabberdctl HTTP listener socket
+#
+# To speedup ejabberdctl execution time for ejabberd commands,
+# you can setup an ejabberd_http listener with ejabberd_ctl handling requests,
+# listening in a unix domain socket.
+#
+# Default: disabled
+#
+#CTL_OVER_HTTP=sockets/ctl_over_http.sock
+
#.
#'
# vim: foldmarker=#',#. foldmethod=marker:
diff --git a/ejabberdctl.template b/ejabberdctl.template
index b9f01535b..7858067d1 100755
--- a/ejabberdctl.template
+++ b/ejabberdctl.template
@@ -179,7 +179,9 @@ livewarning()
echo "Please be extremely cautious with your actions,"
echo "and exit immediately if you are not completely sure."
echo ""
- echo "To exit and detach this shell from ejabberd, press:"
+ echo "To stop ejabberd gracefully:"
+ echo " ejabberd:stop()."
+ echo "To quit erlang immediately, press:"
echo " control+g and then q"
echo ""
echo "--------------------------------------------------------------------"
@@ -332,6 +334,54 @@ wait_status()
[ $timeout -gt 0 ]
}
+exec_other_command()
+{
+ if [ -z "$CTL_OVER_HTTP" ] || [ ! -S "$CTL_OVER_HTTP" ] \
+ || [ ! -x "$(command -v curl)" ] || [ -z "$1" ] || [ "$1" = "help" ] \
+ || [ "$1" = "mnesia_info_ctl" ]|| [ "$1" = "print_sql_schema" ] ; then
+ exec_erl "$(uid ctl)" -hidden -noinput \
+ -eval 'net_kernel:connect_node('"'$ERLANG_NODE'"')' \
+ -s ejabberd_ctl \
+ -extra "$ERLANG_NODE" $NO_TIMEOUT "$@"
+ result=$?
+ case $result in
+ 3) help;;
+ *) :;;
+ esac
+ exit $result
+ else
+ exec_ctl_over_http_socket "$@"
+ fi
+}
+
+exec_ctl_over_http_socket()
+{
+ COMMAND=${1}
+ CARGS=""
+ while [ $# -gt 0 ]; do
+ [ -z "$CARGS" ] && CARGS="[" || CARGS="${CARGS}, "
+ CARGS="${CARGS}\"$1\""
+ shift
+ done
+ CARGS="${CARGS}]"
+ TEMPHEADERS=temp-headers.log
+ curl \
+ --unix-socket ${CTL_OVER_HTTP} \
+ --header "Content-Type: application/json" \
+ --header "Accept: application/json" \
+ --data "${CARGS}" \
+ --dump-header ${TEMPHEADERS} \
+ --no-progress-meter \
+ "http://localhost/ctl/${COMMAND}"
+ result=$(sed -n 's/.*status-code: \([0-9]*\).*/\1/p' < $TEMPHEADERS)
+ rm ${TEMPHEADERS}
+ case $result in
+ 2|3) exec_other_command help ${COMMAND};;
+ *) :;;
+ esac
+ exit $result
+}
+
# ensure we can change current directory to SPOOL_DIR
[ -d "$SPOOL_DIR" ] || exec_cmd mkdir -p "$SPOOL_DIR"
cd "$SPOOL_DIR" || {
@@ -400,15 +450,6 @@ case $1 in
;;
*)
set_dist_client
- exec_erl "$(uid ctl)" -hidden -noinput \
- -eval 'net_kernel:connect_node('"'$ERLANG_NODE'"')' \
- -s ejabberd_ctl \
- -extra "$ERLANG_NODE" $NO_TIMEOUT "$@"
- result=$?
- case $result in
- 2|3) help;;
- *) :;;
- esac
- exit $result
+ exec_other_command "$@"
;;
esac
diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl
index c6e955ab2..259831d7f 100644
--- a/src/ejabberd_admin.erl
+++ b/src/ejabberd_admin.erl
@@ -663,7 +663,7 @@ status() ->
{value, {_, _, Version}} ->
{ok, io_lib:format("ejabberd ~s is running in that node", [Version])}
end,
- {Is_running, String1 ++ " " ++String2}.
+ {Is_running, String1 ++ "\n" ++String2}.
stop() ->
_ = supervisor:terminate_child(ejabberd_sup, ejabberd_sm),
diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl
index 6fcd46e1c..72aac3780 100644
--- a/src/ejabberd_ctl.erl
+++ b/src/ejabberd_ctl.erl
@@ -28,7 +28,7 @@
-behaviour(gen_server).
-author('alexey@process-one.net').
--export([start/0, start_link/0, process/1, process2/2]).
+-export([start/0, start_link/0, process/1, process/2, process2/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
@@ -37,6 +37,7 @@
-include("ejabberd_ctl.hrl").
-include("ejabberd_commands.hrl").
+-include("ejabberd_http.hrl").
-include("logger.hrl").
-include("ejabberd_stacktrace.hrl").
@@ -115,15 +116,44 @@ code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%-----------------------------
-%% Process
+%% Process http
+%%-----------------------------
+
+-spec process_http([binary()], tuple()) -> {non_neg_integer(), [{binary(), binary()}], string()}.
+
+process_http([_Call], #request{data = Data, path = [<<"ctl">> | _]}) ->
+ Args = [binary_to_list(E) || E <- misc:json_decode(Data)],
+ process_http2(Args, ?DEFAULT_VERSION).
+
+process_http2(["--version", Arg | Args], _) ->
+ Version =
+ try
+ list_to_integer(Arg)
+ catch _:_ ->
+ throw({invalid_version, Arg})
+ end,
+ process_http2(Args, Version);
+
+process_http2(Args, Version) ->
+ {String, Code} = process2(Args, [], Version),
+ String2 = case String of
+ [] -> String;
+ _ -> [String, "\n"]
+ end,
+ {200, [{<<"status-code">>, integer_to_binary(Code)}], String2}.
+
+%%-----------------------------
+%% Process command line
%%-----------------------------
-spec process([string()]) -> non_neg_integer().
process(Args) ->
process(Args, ?DEFAULT_VERSION).
+-spec process([string() | binary()], non_neg_integer() | tuple()) -> non_neg_integer().
--spec process([string()], non_neg_integer()) -> non_neg_integer().
+process([Call], Request) when is_binary(Call) and is_record(Request, request) ->
+ process_http([Call], Request);
%% The commands status, stop and restart are defined here to ensure
%% they are usable even if ejabberd is completely stopped.
@@ -232,7 +262,7 @@ process2(Args, AccessCommands, Auth, Version) ->
io:format(lists:flatten(["\n" | String]++["\n"])),
[CommandString | _] = Args,
process(["help" | [CommandString]], Version),
- {lists:flatten(String), ?STATUS_ERROR};
+ {lists:flatten(String), ?STATUS_USAGE};
{String, Code}
when is_list(String) and is_integer(Code) ->
{lists:flatten(String), Code};
@@ -271,7 +301,7 @@ try_run_ctp(Args, Auth, AccessCommands, Version) ->
try_call_command(Args, Auth, AccessCommands, Version);
false ->
print_usage(Version),
- {"", ?STATUS_USAGE};
+ {"", ?STATUS_BADRPC};
Status ->
{"", Status}
catch
@@ -288,7 +318,7 @@ try_run_ctp(Args, Auth, AccessCommands, Version) ->
try_call_command(Args, Auth, AccessCommands, Version) ->
try call_command(Args, Auth, AccessCommands, Version) of
{Reason, wrong_command_arguments} ->
- {Reason, ?STATUS_ERROR};
+ {Reason, ?STATUS_USAGE};
Res ->
Res
catch
@@ -550,7 +580,8 @@ print_usage(HelpMode, MaxC, ShCode, Version) ->
AllCommands = get_list_commands(Version),
print(
- ["Usage: ", "ejabberdctl", " [--no-timeout] [--node ", ?A("nodename"), "] [--version ", ?A("api_version"), "] ",
+ ["Usage: ", "ejabberdctl", " [--no-timeout] [--node ", ?A("name"), "] [--version ", ?A("apiv"), "] ",
+ "[--auth ", ?A("user host pass"), "] ",
?C("command"), " [", ?A("arguments"), "]\n"
"\n"
"Available commands in this ejabberd node:\n"], []),
diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl
index 2412eb545..67f3db2de 100644
--- a/src/ejabberd_listener.erl
+++ b/src/ejabberd_listener.erl
@@ -216,18 +216,13 @@ listen_tcp(Port, SockOpts) ->
setup_provisional_udsocket_dir(DefinitivePath) ->
ProvisionalPath = get_provisional_udsocket_path(DefinitivePath),
- SocketDir = filename:dirname(ProvisionalPath),
- file:make_dir(SocketDir),
- file:change_mode(SocketDir, 8#00700),
?DEBUG("Creating a Unix Domain Socket provisional file at ~ts for the definitive path ~s",
[ProvisionalPath, DefinitivePath]),
ProvisionalPath.
get_provisional_udsocket_path(Path) ->
- MnesiaDir = mnesia:system_info(directory),
- SocketDir = filename:join(MnesiaDir, "socket"),
PathBase64 = misc:term_to_base64(Path),
- PathBuild = filename:join(SocketDir, PathBase64),
+ PathBuild = filename:join(os:getenv("HOME"), PathBase64),
%% Shorthen the path, a long path produces a crash when opening the socket.
binary:part(PathBuild, {0, erlang:min(107, byte_size(PathBuild))}).
@@ -236,11 +231,10 @@ get_definitive_udsocket_path(<<"unix", _>> = Unix) ->
get_definitive_udsocket_path(ProvisionalPath) ->
PathBase64 = filename:basename(ProvisionalPath),
{term, Path} = misc:base64_to_term(PathBase64),
- Path.
+ relative_socket_to_mnesia(Path).
set_definitive_udsocket(<<"unix:", Path/binary>>, Opts) ->
Prov = get_provisional_udsocket_path(Path),
- timer:sleep(5000),
Usd = maps:get(unix_socket, Opts),
case maps:get(mode, Usd, undefined) of
undefined -> ok;
@@ -268,10 +262,33 @@ set_definitive_udsocket(<<"unix:", Path/binary>>, Opts) ->
throw({error_setting_socket_group, Group, Prov})
end
end,
- file:rename(Prov, Path);
+ FinalPath = relative_socket_to_mnesia(Path),
+ FinalPathDir = filename:dirname(FinalPath),
+ case file:make_dir(FinalPathDir) of
+ ok ->
+ file:change_mode(FinalPathDir, 8#00700);
+ _ ->
+ ok
+ end,
+ file:rename(Prov, FinalPath);
set_definitive_udsocket(_Port, _Opts) ->
ok.
+relative_socket_to_mnesia(Path1) ->
+ case filename:pathtype(Path1) of
+ absolute ->
+ Path1;
+ relative ->
+ MnesiaDir = mnesia:system_info(directory),
+ filename:join(MnesiaDir, Path1)
+ end.
+
+maybe_delete_udsocket_file(<<"unix:", Path/binary>>) ->
+ PathAbsolute = relative_socket_to_mnesia(Path),
+ file:delete(PathAbsolute);
+maybe_delete_udsocket_file(_Port) ->
+ ok.
+
%%%
%%%
%%%
@@ -341,11 +358,20 @@ accept(ListenSocket, Module, State, Sup, Interval, Proxy, Arity) ->
gen_tcp:close(Socket),
none
end,
- ?INFO_MSG("(~p) Accepted connection ~ts -> ~ts",
- [Receiver,
- ejabberd_config:may_hide_data(
- format_endpoint({PPort, PAddr, tcp})),
- format_endpoint({Port, Addr, tcp})]);
+ case is_ctl_over_http(State) of
+ false ->
+ ?INFO_MSG("(~p) Accepted connection ~ts -> ~ts",
+ [Receiver,
+ ejabberd_config:may_hide_data(
+ format_endpoint({PPort, PAddr, tcp})),
+ format_endpoint({Port, Addr, tcp})]);
+ true ->
+ ?DEBUG("(~p) Accepted connection ~ts -> ~ts",
+ [Receiver,
+ ejabberd_config:may_hide_data(
+ format_endpoint({PPort, PAddr, tcp})),
+ format_endpoint({Port, Addr, tcp})])
+ end;
_ ->
gen_tcp:close(Socket)
end,
@@ -356,6 +382,16 @@ accept(ListenSocket, Module, State, Sup, Interval, Proxy, Arity) ->
accept(ListenSocket, Module, State, Sup, NewInterval, Proxy, Arity)
end.
+is_ctl_over_http(State) ->
+ case lists:keyfind(request_handlers, 1, State) of
+ {request_handlers, Handlers} ->
+ case lists:keyfind(ejabberd_ctl, 2, Handlers) of
+ {_, ejabberd_ctl} -> true;
+ _ -> false
+ end;
+ _ -> false
+ end.
+
-spec udp_recv(inet:socket(), module(), state()) -> no_return().
udp_recv(Socket, Module, State) ->
case gen_udp:recv(Socket, 0) of
@@ -470,12 +506,13 @@ stop_listeners() ->
Ports).
-spec stop_listener(endpoint(), module(), opts()) -> ok | {error, any()}.
-stop_listener({_, _, Transport} = EndPoint, Module, Opts) ->
+stop_listener({Port, _, Transport} = EndPoint, Module, Opts) ->
case supervisor:terminate_child(?MODULE, EndPoint) of
ok ->
?INFO_MSG("Stop accepting ~ts connections at ~ts for ~p",
[format_transport(Transport, Opts),
format_endpoint(EndPoint), Module]),
+ maybe_delete_udsocket_file(Port),
ets:delete(?MODULE, EndPoint),
supervisor:delete_child(?MODULE, EndPoint);
Err ->
@@ -562,10 +599,12 @@ format_error(Reason) ->
end.
-spec format_endpoint(endpoint()) -> string().
-format_endpoint({Port, IP, _Transport}) ->
+format_endpoint({Port, IP, Transport}) ->
case Port of
<<"unix:", _/binary>> ->
Port;
+ <<>> when (IP == local) and (Transport == tcp) ->
+ "local-unix-socket-domain";
Unix when is_binary(Unix) ->
Def = get_definitive_udsocket_path(Unix),
<<"unix:", Def/binary>>;