diff --git a/.github/workflows/code-server.yml b/.github/workflows/code-server.yml new file mode 100644 index 0000000..27c6f26 --- /dev/null +++ b/.github/workflows/code-server.yml @@ -0,0 +1,42 @@ +name: code-server + +on: + push: + paths: + - '.github/workflows/code-server.yml' + - 'code-server/**' + +jobs: + + code-server: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + + - uses: actions/checkout@v4 + + - name: Build image + run: docker build + code-server + --tag code-server + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push image + run: | + IMAGE_ID=ghcr.io/${{ github.repository_owner }}/code-server + # Strip git ref prefix from version + VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') + # Use Docker `latest` tag convention + [ "$VERSION" == "master" ] && VERSION=latest + docker tag code-server $IMAGE_ID:$VERSION + docker push $IMAGE_ID:$VERSION diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml new file mode 100644 index 0000000..cd6e19e --- /dev/null +++ b/.github/workflows/devcontainer.yml @@ -0,0 +1,42 @@ +name: devcontainer + +on: + push: + paths: + - '.github/workflows/devcontainer.yml' + - 'devcontainer/**' + +jobs: + + devcontainer: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + + - uses: actions/checkout@v4 + + - name: Build image + run: docker build + devcontainer + --tag devcontainer + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push image + run: | + IMAGE_ID=ghcr.io/${{ github.repository_owner }}/devcontainer + # Strip git ref prefix from version + VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') + # Use Docker `latest` tag convention + [ "$VERSION" == "master" ] && VERSION=latest + docker tag devcontainer $IMAGE_ID:$VERSION + docker push $IMAGE_ID:$VERSION diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 97dbd59..78743de 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,44 +2,193 @@ name: Tests on: push: - paths-ignore: - - '**.md' - - '**.bat' + paths: + - '.github/workflows/tests.yml' + - 'ecs/**' + - 'mix/**' pull_request: - paths-ignore: - - '**.md' - - '**.bat' + paths: + - '.github/workflows/tests.yml' + - 'ecs/**' + - 'mix/**' + +env: + REGISTRY_GHCR: ghcr.io + REGISTRY_LOCAL: localhost:5000 + REGISTRY_DOCKER: docker.io + MIX_IMAGENAME_GHCR: ${{ github.repository_owner }}/mix + ECS_IMAGENAME_GHCR: ${{ github.repository_owner }}/ecs + MIX_IMAGENAME_DOCKERHUB: ejabberd/mix + ECS_IMAGENAME_DOCKERHUB: ejabberd/ecs jobs: + build: name: Build - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: fail-fast: true max-parallel: 1 + permissions: + packages: write + services: + registry: + image: registry:2 + ports: + - 5000:5000 steps: + - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY_GHCR }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build ecs image + - name: Get git describe + id: gitdescribe + run: echo "ver=$(git describe --tags --exact-match 2>/dev/null || echo latest)" >> $GITHUB_OUTPUT + + - name: Extract mix metadata (tags, labels) + id: mixmeta + if: github.ref_type == 'tag' + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.REGISTRY_GHCR }}/${{ env.MIX_IMAGENAME_GHCR }} + ${{ env.REGISTRY_DOCKER }}/${{ env.MIX_IMAGENAME_DOCKERHUB }} + labels: | + org.opencontainers.image.revision=${{ steps.gitdescribe.outputs.ver }} + org.opencontainers.image.licenses=GPL-2.0 + org.opencontainers.image.vendor=ProcessOne + + - name: Extract ecs metadata (tags, labels) + id: ecsmeta + if: github.ref_type == 'tag' + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.REGISTRY_GHCR }}/${{ env.ECS_IMAGENAME_GHCR }} + ${{ env.REGISTRY_DOCKER }}/${{ env.ECS_IMAGENAME_DOCKERHUB }} + labels: | + org.opencontainers.image.revision=${{ steps.gitdescribe.outputs.ver }} + org.opencontainers.image.licenses=GPL-2.0 + org.opencontainers.image.vendor=ProcessOne + + - name: Prepare local tags + id: localreg run: | - cd ecs - ./build.sh + tag="$(echo ${{ github.ref_name }} | sed -e 's|[/]\+|-|g')" + echo "mixlocaltag=${{ env.REGISTRY_LOCAL }}/${{ env.MIX_IMAGENAME_GHCR }}:$tag" >> $GITHUB_OUTPUT + echo "ecslocaltag=${{ env.REGISTRY_LOCAL }}/${{ env.ECS_IMAGENAME_GHCR }}:$tag" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host + + - name: Build and push local mix image + uses: docker/build-push-action@v5 + with: + build-args: | + VERSION=${{ steps.gitdescribe.outputs.ver }} + cache-from: type=gha + cache-to: type=gha,mode=max + context: mix + labels: ${{ steps.mixmeta.outputs.labels }} + platforms: linux/amd64 + push: true + tags: | + ${{ steps.localreg.outputs.mixlocaltag }} + + - name: Prepare ecs Dockerfile + run: sed -i 's|docker.io/ejabberd/mix|${{ steps.localreg.outputs.mixlocaltag }}|g' ecs/Dockerfile + + - name: Build and push local ecs image + uses: docker/build-push-action@v5 + with: + build-args: | + VERSION=${{ steps.gitdescribe.outputs.ver }} + cache-from: type=gha + cache-to: type=gha,mode=max + context: ecs + labels: ${{ steps.ecsmeta.outputs.labels }} + platforms: linux/amd64 + push: true + tags: | + ${{ steps.localreg.outputs.ecslocaltag }} - name: Run ecs image run: | docker images - docker run --name ejabberd -d -p 5222:5222 ejabberd/ecs:latest + docker run --name ejabberd -d -p 5222:5222 ${{ steps.localreg.outputs.ecslocaltag }} - name: Wait ejabberd started run: | docker exec ejabberd bin/ejabberdctl started - name: Check ecs results + if: always() run: | docker ps -s docker logs ejabberd docker logs ejabberd | grep -q "Start accepting TCP connections" || exit 1 docker logs ejabberd | grep -q "error" && exit 1 || exit 0 docker logs ejabberd | grep -q "Error" && exit 1 || exit 0 + + - name: Save image + run: | + docker image save ${{ steps.localreg.outputs.ecslocaltag }} --output ejabberd-latest.tar + + - name: Upload image + uses: actions/upload-artifact@v4 + with: + name: ejabberd-image + path: ejabberd-latest.tar + + - run: | + echo "::notice::To get this image, download ejabberd-image.zip, "\ + "uncompress it and run: " \ + "docker image load -i ejabberd-latest.tar" + + - name: Build and push mix image + uses: docker/build-push-action@v5 + if: github.ref_type == 'tag' + with: + build-args: | + VERSION=${{ steps.gitdescribe.outputs.ver }} + cache-from: type=gha + cache-to: type=gha,mode=max + context: mix + labels: ${{ steps.mixmeta.outputs.labels }} + platforms: linux/amd64 + push: true + tags: | + ${{ steps.mixmeta.outputs.tags }} + + - name: Build and push ecs image + uses: docker/build-push-action@v5 + if: github.ref_type == 'tag' + with: + build-args: | + VERSION=${{ steps.gitdescribe.outputs.ver }} + cache-from: type=gha + cache-to: type=gha,mode=max + context: ecs + labels: ${{ steps.ecsmeta.outputs.labels }} + platforms: linux/amd64 + push: true + tags: | + ${{ steps.ecsmeta.outputs.tags }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8504195..0000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -os: linux - -dist: trusty - -services: docker - -language: shell - -before_script: - - env | sort - - dir="${VARIANT}/" - -script: - - cd "$dir" - - travis_wait docker build --build-arg VERSION=latest -t ejabberd/ecs:travis-latest . - - docker images - - travis_retry docker run --name ejabberd -d -p 5222:5222 ejabberd/ecs && sleep 60 - - docker ps - - docker logs ejabberd - - docker logs ejabberd | grep "Start accepting TCP connections" || exit 1 - -notifications: - email: false - -env: - - VARIANT=ecs - - VARIANT=mix diff --git a/README.md b/README.md index 2b09acc..ca3e312 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,33 @@ # docker-ejabberd -This repository contains a set of Docker images for ejabberd. +This repository contains a set of container images to run or develop ejabberd: -- [ejabberd/mix](https://hub.docker.com/r/ejabberd/mix/): This image allows you to build develop - environment for ejabberd, using all dependencies packaged from the Docker image. You do not - need anything else to build ejabberd from source and write your own ejabberd plugins. -- [ejabberd/ecs](https://hub.docker.com/r/ejabberd/ecs/): This image is suitable for running - ejabberd with Docker in a simple, single-node setup. +- [mix](mix/) (published in [docker.io/ejabberd/mix](https://hub.docker.com/r/ejabberd/mix/) +and [ghcr.io/processone/mix](https://github.com/processone/docker-ejabberd/pkgs/container/mix)): -Please read the README file in each repository for documentation for each image. + Build a development environment for ejabberd. See [mix README](mix/README.md) file for details. + +- [ecs](ecs/) (published in [docker.io/ejabberd/ecs](https://hub.docker.com/r/ejabberd/ecs/) +and [ghcr.io/processone/ecs](https://github.com/processone/docker-ejabberd/pkgs/container/ecs)): + + Run ejabberd in a container with simple setup. + See [ecs README](ecs/README.md) file for details. + +- [code-server](code-server/) (published in [ghcr.io/processone/code-server](https://github.com/orgs/processone/packages/container/package/code-server)): + + Run Coder's code-server with a local ejabberd git clone. + See [VSCode section](https://docs.ejabberd.im/developer/vscode/) in ejabberds Docs. + +- [devcontainer](devcontainer/) (published in [ghcr.io/processone/devcontainer](https://github.com/orgs/processone/packages/container/package/devcontainer)): + + Use as a Dev Container for ejabberd in Visual Studio Code. + See [VSCode section](https://docs.ejabberd.im/developer/vscode/) in ejabberds Docs. + +The [ejabberd source code repository](https://github.com/processone/ejabberd) also provides: + +- [ejabberd](https://github.com/processone/ejabberd/tree/master/.github/container) (published in [ghcr.io/processone/ejabberd](https://github.com/processone/ejabberd/pkgs/container/ejabberd)): + + Run ejabberd in a container with simple setup. + See [ejabberd's CONTAINER](https://github.com/processone/ejabberd/blob/master/CONTAINER.md) file for details. + Check the [differences between this image and `ecs`](https://github.com/processone/docker-ejabberd/blob/master/ecs/HUB-README.md#alternative-image-in-github). diff --git a/code-server/Dockerfile b/code-server/Dockerfile new file mode 100644 index 0000000..405b7d0 --- /dev/null +++ b/code-server/Dockerfile @@ -0,0 +1,30 @@ +FROM debian:sid-slim + +RUN apt-get update \ + && apt-get -y --no-install-recommends install \ + curl ca-certificates \ + autoconf automake git make gcc g++ \ + erlang erlang-dev elixir rebar3 \ + libexpat1-dev libgd-dev libpam0g-dev \ + libsqlite3-dev libwebp-dev libyaml-dev \ + libssl-dev + +RUN curl -fsSL https://code-server.dev/install.sh | sh + +RUN addgroup vscode --gid 1000 \ + && adduser --shell /bin/bash --ingroup vscode vscode -u 1000 + +USER vscode + +RUN /usr/bin/code-server --install-extension erlang-ls.erlang-ls + +WORKDIR /home/vscode +RUN echo "export PATH=/workspaces/ejabberd/_build/relive:$PATH" >>.bashrc \ + && echo "COOKIE" >.erlang.cookie \ + && chmod 400 .erlang.cookie + +WORKDIR /workspaces/ejabberd +VOLUME ["workspaces/ejabberd"] +EXPOSE 1870 1883 4369-4399 5210 5222 5269 5280 5443 + +ENTRYPOINT ["code-server", "--bind-addr", "0.0.0.0:1870", "--auth", "none", "/workspaces/ejabberd"] diff --git a/devcontainer/Dockerfile b/devcontainer/Dockerfile new file mode 100644 index 0000000..4435eb2 --- /dev/null +++ b/devcontainer/Dockerfile @@ -0,0 +1,65 @@ +# [Choice] Alpine version: 3.16, 3.15, 3.14, 3.13 +ARG VARIANT=latest +FROM alpine:${VARIANT} + +RUN apk upgrade --update musl \ + && apk add \ + autoconf \ + automake \ + bash \ + build-base \ + curl \ + elixir \ + erlang-debugger \ + erlang-observer \ + erlang-odbc \ + erlang-reltool \ + expat-dev \ + file \ + gd-dev \ + git \ + jpeg-dev \ + libpng-dev \ + libwebp-dev \ + linux-pam-dev \ + openssl \ + openssl-dev \ + sqlite-dev \ + yaml-dev \ + zlib-dev + +# [Option] Install zsh +ARG INSTALL_ZSH="true" + +# Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies. +ARG USERNAME=vscode +ARG USER_UID=1000 +ARG USER_GID=$USER_UID +ARG COMMON_SCRIPT_SOURCE="https://raw.githubusercontent.com/devcontainers/images/v0.2.31/src/base-alpine/.devcontainer/library-scripts/common-alpine.sh" +RUN apk update \ + && curl -sSL ${COMMON_SCRIPT_SOURCE} -o /tmp/common-alpine.sh \ + && ash /tmp/common-alpine.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" \ + && rm -rf /tmp/common-alpine.sh + +RUN mix local.hex --force \ + && mix local.rebar --force + +RUN apk add \ + expat \ + freetds \ + gd \ + jpeg \ + libgd \ + libpng \ + libstdc++ \ + libwebp \ + linux-pam \ + ncurses-libs \ + openssl \ + sqlite \ + sqlite-libs \ + unixodbc \ + yaml \ + zlib \ + && ln -fs /usr/lib/libtdsodbc.so.0 /usr/lib/libtdsodbc.so \ + && rm -rf /var/cache/apk/* diff --git a/ecs/Dockerfile b/ecs/Dockerfile index 3443d52..726d74c 100644 --- a/ecs/Dockerfile +++ b/ecs/Dockerfile @@ -1,4 +1,9 @@ -FROM ejabberd/mix as builder +FROM docker.io/golang:1.25-alpine AS api +RUN go install -v \ + github.com/processone/ejabberd-api/cmd/ejabberd@latest \ + && mv bin/ejabberd bin/ejabberdapi + +FROM docker.io/ejabberd/mix AS builder ARG VERSION ENV VERSION=${VERSION:-latest} \ MIX_ENV=prod @@ -9,34 +14,41 @@ LABEL maintainer="ProcessOne " \ RUN git clone https://github.com/processone/ejabberd.git WORKDIR /ejabberd COPY vars.config . -RUN echo '{vsn, "'${VERSION}'.0"}.' >>vars.config COPY config.exs config/ COPY rel/*exs rel/ RUN git checkout ${VERSION/latest/HEAD} \ + \ + && if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+$ ]]; then \ + echo '{vsn, "'"$VERSION.0"'"}.' >> vars.config; \ + else \ + echo '{vsn, "0.0.0"}.' >> vars.config; \ + fi \ + \ && mix deps.get \ && (cd deps/eimp; ./configure) # Compile -RUN mix do compile, distillery.init, distillery.release --env=prod +RUN MIX_ENV=prod mix release # Prepare runtime environment RUN mkdir runtime \ - && tar -C runtime -zxf _build/prod/rel/ejabberd/releases/*/ejabberd.tar.gz \ + && tar -C runtime -zxf _build/prod/ejabberd-*.tar.gz \ && cd runtime \ && cp releases/*/start.boot bin \ - && echo 'beam_lib:strip_files(filelib:wildcard("lib/*/ebin/*beam")), init:stop().' | erts*/bin/erl -boot start_clean >/dev/null \ + && cp releases/*/start_clean.boot bin \ + && echo 'beam_lib:strip_files(filelib:wildcard("lib/*/ebin/*beam")), init:stop().' | erl >/dev/null \ && mv erts*/bin/* bin \ && EJABBERD_VERSION=`(cd releases; ls -1 -d *.*.*)` \ - && rm -rf releases erts* bin/*src bin/dialyzer bin/typer \ - && rm bin/ejabberd bin/ejabberd.bat \ + && rm -rf releases erts* bin/*src bin/dialyzer bin/typer etc \ + && rm bin/ejabberd \ && mkdir lib/ejabberd-$EJABBERD_VERSION/priv/bin \ && cp /usr/lib/elixir/bin/* bin/ \ && sed -i 's|ERL_EXEC="erl"|ERL_EXEC="/home/ejabberd/bin/erl"|' bin/elixir \ - && cp /ejabberd/tools/captcha*sh lib/ejabberd-$EJABBERD_VERSION/priv/bin \ + && cp /ejabberd/tools/captcha*sh bin/ \ && cp -r /ejabberd/sql lib/ejabberd-*/priv # Runtime container -FROM alpine:3.11 +FROM docker.io/alpine:3.22 ARG VERSION ARG VCS_REF ARG BUILD_DATE @@ -46,6 +58,7 @@ ENV TERM=xterm \ LANGUAGE=en_US.UTF-8 \ REPLACE_OS_VARS=true \ HOME=/home/ejabberd \ + PATH="$PATH:/home/ejabberd/bin" \ VERSION=${VERSION:-latest} LABEL maintainer="ProcessOne " \ product="Ejabberd Community Server Official Docker Image" \ @@ -64,10 +77,12 @@ LABEL maintainer="ProcessOne " \ RUN addgroup ejabberd -g 9000 \ && adduser -s /bin/sh -D -G ejabberd ejabberd -u 9000 \ && mkdir -p /home/ejabberd/conf /home/ejabberd/database /home/ejabberd/logs /home/ejabberd/upload \ - && chown -R ejabberd:ejabberd /home/ejabberd + && chown -R ejabberd:ejabberd /home/ejabberd \ + && ln -fs /home/ejabberd /opt/ejabberd \ + && ln -fs /home/ejabberd /opt/ejabberd-$VERSION # Install required dependencies -RUN apk upgrade --update musl \ +RUN apk upgrade --update-cache --no-progress \ && apk add \ expat \ freetds \ @@ -81,6 +96,7 @@ RUN apk upgrade --update musl \ openssl \ sqlite \ sqlite-libs \ + tini \ unixodbc \ yaml \ zlib \ @@ -91,14 +107,18 @@ RUN apk upgrade --update musl \ WORKDIR $HOME COPY --from=builder /ejabberd/runtime . COPY bin/* bin/ -RUN chmod 755 bin/ejabberdctl bin/ejabberdapi bin/erl +COPY --from=api /go/bin/ejabberdapi bin/ejabberdapi +RUN chmod 755 bin/ejabberdctl bin/ejabberdapi bin/erl bin/captcha*.sh \ + && mkdir -p /home/ejabberd/sql \ + && cp /home/ejabberd/lib/ejabberd-*/priv/sql/* /home/ejabberd/database/ \ + && cp /home/ejabberd/lib/ejabberd-*/priv/sql/* /home/ejabberd/sql/ COPY --chown=ejabberd:ejabberd conf conf/ ADD --chown=ejabberd:ejabberd https://download.process-one.net/cacert.pem conf/cacert.pem # Setup runtime environment USER ejabberd VOLUME ["$HOME/database","$HOME/conf","$HOME/logs","$HOME/upload"] -EXPOSE 1883 4369-4399 5222 5269 5280 5443 +EXPOSE 1880 1883 4369-4399 5210 5222 5269 5280 5443 5478 7777 50000-50099 -ENTRYPOINT ["/home/ejabberd/bin/ejabberdctl"] +ENTRYPOINT ["/sbin/tini","--","/home/ejabberd/bin/ejabberdctl"] CMD ["foreground"] diff --git a/ecs/HUB-README.md b/ecs/HUB-README.md new file mode 100644 index 0000000..2871cf6 --- /dev/null +++ b/ecs/HUB-README.md @@ -0,0 +1,58 @@ +# ejabberd Community Server Docker Image + +## What is ejabberd + +[ejabberd][im] is an open-source, +robust, scalable and extensible realtime platform built using [Erlang/OTP][erlang], +that includes [XMPP][xmpp] Server, [MQTT][mqtt] Broker and [SIP][sip] Service. + +Check the features in [ejabberd.im][im], [ejabberd Docs][features], +[ejabberd at ProcessOne][p1home], and a list of [supported protocols and XEPs][xeps]. + +[im]: https://ejabberd.im/ +[erlang]: https://www.erlang.org/ +[xmpp]: https://xmpp.org/ +[mqtt]: https://mqtt.org/ +[sip]: https://en.wikipedia.org/wiki/Session_Initiation_Protocol +[features]: https://docs.ejabberd.im/admin/introduction/ +[p1home]: https://www.process-one.net/en/ejabberd/ +[xeps]: https://www.process-one.net/en/ejabberd/protocols/ + + +## What is `ejabberd/ecs` + +This `ejabberd/ecs` Docker image is built for stable ejabberd releases using +[docker-ejabberd/ecs](https://github.com/processone/docker-ejabberd/tree/master/ecs). +It's based in Alpine Linux, and is aimed at providing a simple image to setup and configure. + +Please report problems related to this `ejabberd/ecs` image packaging in +[docker-ejabberd Issues](https://github.com/processone/docker-ejabberd/issues), +and general ejabberd problems in +[ejabberd Issues](https://github.com/processone/ejabberd/issues). + + +## How to use the ejabberd/ecs image + +Please check [ejabberd/ecs README](https://github.com/processone/docker-ejabberd/tree/master/ecs#readme) + + +## Supported Architectures + +This `ejabberd/ecs` docker image is built for the `linux/amd64` architecture. + + +## Alternative Image in GitHub + +There is another container image published in +[ejabberd GitHub Packages](https://github.com/processone/ejabberd/pkgs/container/ejabberd), +that you can download from the GitHub Container Registry. + +Its usage is similar to this `ejabberd/ecs` image, with some benefits and changes worth noting: + +- it's available for `linux/amd64` and `linux/arm64` architectures +- it's built also for `master` branch, in addition to the stable ejabberd releases +- it includes less customizations to the base ejabberd compared to `ejabberd/ecs` +- it stores data in `/opt/ejabberd/` instead of `/home/ejabberd/` + +See its documentation in [CONTAINER](https://github.com/processone/ejabberd/blob/master/CONTAINER.md). + diff --git a/ecs/README.md b/ecs/README.md index 765e202..a06bc97 100644 --- a/ecs/README.md +++ b/ecs/README.md @@ -1,106 +1,182 @@ -[![Docker Image Version (latest by date)](https://img.shields.io/docker/v/ejabberd/ecs)](https://hub.docker.com/r/ejabberd/ecs/) -[![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/ejabberd/ecs)](https://hub.docker.com/r/ejabberd/ecs/) -[![Docker Stars](https://img.shields.io/docker/stars/ejabberd/ecs)](https://hub.docker.com/r/ejabberd/ecs/) -[![Docker Pulls](https://img.shields.io/docker/pulls/ejabberd/ecs)](https://hub.docker.com/r/ejabberd/ecs/) -[![Build Status](https://github.com/processone/docker-ejabberd/actions/workflows/tests.yml/badge.svg)](https://github.com/processone/docker-ejabberd/actions/workflows/tests.yml) -[![GitHub stars](https://img.shields.io/github/stars/processone/docker-ejabberd?style=social)](https://github.com/processone/docker-ejabberd) +[![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/processone/ejabberd?sort=semver&logo=embarcadero&label=&color=49c0c4)](https://github.com/processone/ejabberd/tags) +[![ejabberd Container on GitHub](https://img.shields.io/github/v/tag/processone/ejabberd?label=ejabberd&sort=semver&logo=opencontainersinitiative&logoColor=2094f3)](https://github.com/processone/ejabberd/pkgs/container/ejabberd) +[![ecs Container on Docker](https://img.shields.io/docker/v/ejabberd/ecs?label=ecs&sort=semver&logo=docker)](https://hub.docker.com/r/ejabberd/ecs/) -# ejabberd Community Server +ejabberd Container Images +========================= -ejabberd is an open-source XMPP server, robust, scalable and modular, -built using Erlang/OTP, and also includes MQTT Broker and SIP Service. +[ejabberd][home] is an open-source, +robust, scalable and extensible realtime platform built using [Erlang/OTP][erlang], +that includes [XMPP][xmpp] Server, [MQTT][mqtt] Broker and [SIP][sip] Service. -This Docker image allows you to run a single node ejabberd instance in a Docker container. +[home]: https://www.ejabberd.im/ +[erlang]: https://www.erlang.org/ +[xmpp]: https://xmpp.org/ +[mqtt]: https://mqtt.org/ +[sip]: https://en.wikipedia.org/wiki/Session_Initiation_Protocol -If you are using a Windows operating system, check the tutorials mentioned in -[ejabberd Docs > Docker Image](https://docs.ejabberd.im/admin/installation/#docker-image). +This page documents those container images ([images comparison](#images-comparison)): -# Start ejabberd +- [![ejabberd Container](https://img.shields.io/badge/ejabberd-grey?logo=opencontainersinitiative&logoColor=2094f3)](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. -## With default configuration +- [![ecs Container](https://img.shields.io/badge/ecs-grey?logo=docker&logoColor=2094f3)](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. -You can start ejabberd in a new container with the following command: +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 +-------------- + +### daemon + +Start ejabberd in a new container: ```bash -docker run --name ejabberd -d -p 5222:5222 ejabberd/ecs +docker run --name ejabberd -d -p 5222:5222 docker.io/ejabberd/ecs ``` -This command will run Docker image as a daemon, -using ejabberd default configuration file and XMPP domain "localhost". +That runs the container as a daemon, +using ejabberd default configuration file and XMPP domain `localhost`. + +Restart the stopped ejabberd container: + +```bash +docker restart ejabberd +``` -To stop the running container, you can run: +Stop the running container: ```bash docker stop ejabberd ``` -If needed, you can restart the stopped ejabberd container with: +Remove the ejabberd container: ```bash -docker restart ejabberd +docker rm ejabberd ``` -## Start with Erlang console attached -If you would like to start ejabberd with an Erlang console attached you can use the `live` command: +### with Erlang console + +Start ejabberd with an interactive Erlang console attached using the `live` command: ```bash -docker run -it -p 5222:5222 ejabberd/ecs live +docker run --name ejabberd -it -p 5222:5222 docker.io/ejabberd/ecs live ``` -This command will use default configuration file and XMPP domain "localhost". +That uses the default configuration file and XMPP domain `localhost`. + -## Start with your configuration and database +### with your data -The following command will pass config file using Docker volume feature -and share local directory to store database: +Pass a configuration file as a volume +and share the local directory to store database: ```bash -mkdir database -docker run -d --name ejabberd -v $(pwd)/ejabberd.yml:/home/ejabberd/conf/ejabberd.yml -v $(pwd)/database:/home/ejabberd/database -p 5222:5222 ejabberd/ecs +mkdir conf && cp ejabberd.yml.example conf/ejabberd.yml + +mkdir database && chown ejabberd database + +docker run --name ejabberd -it \ + -v $(pwd)/conf/ejabberd.yml:/opt/ejabberd/conf/ejabberd.yml \ + -v $(pwd)/database:/opt/ejabberd/database \ + -p 5222:5222 docker.io/ejabberd/ecs live +``` + +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 admin account + +#### [![ejabberd Container](https://img.shields.io/badge/ejabberd-grey?logo=opencontainersinitiative&logoColor=2094f3)](https://github.com/processone/ejabberd/pkgs/container/ejabberd) [🔅](#images-comparison) + +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: + +```bash +$ podman run -it \ + --env EJABBERD_MACRO_HOST=example.org \ + --env EJABBERD_MACRO_ADMIN=juliet@example.org \ + --env REGISTER_ADMIN_PASSWORD=somePassw0rd \ + docker.io/ejabberd/ecs + +:> ejabberdctl register juliet example.org somePassw0rd +User juliet@example.org successfully registered ``` -# Next steps +This is implemented internally by using +[Commands on start](#commands-on-start). + +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). + +--- -## Register the administrator account +#### [![ecs Container](https://img.shields.io/badge/ecs-grey?logo=docker&logoColor=2094f3)](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 such an account -to start using it for administrative purposes. -You can register this account using the `ejabberdctl` script, for example: +so you just need to register it, for example: ```bash -docker exec -it ejabberd bin/ejabberdctl register admin localhost passw0rd +docker exec -it ejabberd ejabberdctl register admin localhost passw0rd ``` -## Check ejabberd log files +### Check ejabberd log -You can execute a Docker command to check the content of the log files from -inside to container, even if you do not put it on a shared persistent drive: +Check the content of the log files inside the container, +even if you do not put it on a shared persistent drive: ```bash docker exec -it ejabberd tail -f logs/ejabberd.log ``` -## Inspect the container files -The container uses Alpine Linux. You can start a shell there with: +### Inspect container files + +The container uses Alpine Linux. Start a shell inside the container: ```bash docker exec -it ejabberd sh ``` -## Open ejabberd debug console -You can open a live debug Erlang console attached to a running container: +### Open debug console + +Open an interactive debug Erlang console attached to a running ejabberd in a running container: ```bash -docker exec -it ejabberd bin/ejabberdctl debug +docker exec -it ejabberd ejabberdctl debug ``` -## CAPTCHA + +### CAPTCHA ejabberd includes two example CAPTCHA scripts. If you want to use any of them, first install some additional required libraries: @@ -114,24 +190,321 @@ Now update your ejabberd configuration file, for example: docker exec -it ejabberd vi conf/ejabberd.yml ``` -and add the required options: +and add this option: +```yaml +captcha_cmd: "$HOME/bin/captcha.sh" ``` -captcha_cmd: /home/ejabberd/lib/ejabberd-21.1.0/priv/bin/captcha.sh + +Finally, reload the configuration file or restart the container: +```bash +docker exec ejabberd ejabberdctl reload_config +``` + +If the CAPTCHA image is not visible, there may be a problem generating it +(the ejabberd log file may show some error message); +or the image URL may not be correctly detected by ejabberd, +in that case you can set the correct URL manually, for example: +```yaml captcha_url: https://localhost:5443/captcha ``` -Finally, reload the configuration file or restart the container: +For more details about CAPTCHA options, please check the +[CAPTCHA](https://docs.ejabberd.im/admin/configuration/basic/#captcha) +documentation section. + + +Advanced +-------- + +### 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 (URL is `admin/`). +- `5443`: With encryption, used for admin interface, API, CAPTCHA, OAuth, Websockets and XMPP BOSH. +- `1880`: For admin interface (URL is `/`, useful for [podman-desktop](https://podman-desktop.io/) and [docker-desktop](https://www.docker.com/products/docker-desktop/)) [🔅](#images-comparison) +- `1883`: Used for MQTT +- `5478` UDP: STUN service 🟠 +- `50000-50099` UDP: TURN service 🟠 +- `7777`: SOCKS5 file transfer proxy 🟠 +- `5210`: Erlang connectivity when `ERL_DIST_PORT` is set, alternative to EPMD [🔅](#images-comparison) +- `4369-4399`: EPMD and Erlang connectivity, used for `ejabberdctl` and clustering + +### Volumes + +ejabberd produces two types of data: log files and database spool files (Mnesia). +This is the kind of data you probably want to store on a persistent or local drive (at least the database). + +The volumes you may want to map: + +- `/opt/ejabberd/conf/`: Directory containing configuration and certificates +- `/opt/ejabberd/database/`: Directory containing Mnesia database. +You should back up or export the content of the directory to persistent storage +(host storage, local storage, any storage plugin) +- `/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 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. + +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](https://docs.ejabberd.im/developer/extending-ejabberd/modules/#your-module-in-ejabberd-modules-with-ejabberd-container). + + +### Commands on start + +The ejabberdctl script reads the `CTL_ON_CREATE` environment variable +the first time the container is started, +and reads `CTL_ON_START` every time the container is started. +Those variables can contain one ejabberdctl command, +or several commands separated with the blankspace and `;` characters. + +If any of those commands returns a failure, the container starting gets aborted. +If there is a command with a result that can be ignored, +prefix that command with `!` + +All this works when starting ejabberd with the default method `foreground`, +not when using `live`, `iexlive`, ... + +This example registers an `admin@localhost` account when the container is first created. +Everytime the container starts, it shows the list of registered accounts, +checks that the admin account exists and password is valid, +changes the password of an account if it exists (ignoring any failure), +and shows the ejabberd starts (check also the [full example](#customized-example)): +```yaml + environment: + - CTL_ON_CREATE=register admin localhost asd + - CTL_ON_START=stats registeredusers ; + check_password admin localhost asd ; + ! change_password bot123 localhost qqq ; + status +``` + +Same example using Podman: ```bash -docker exec ejabberd bin/ejabberdctl reload_config +$ podman run -it \ + --env CTL_ON_CREATE="register admin localhost asd" \ + --env CTL_ON_START="stats registeredusers ; \ + check_password admin localhost asd ; \ + ! change_password bot123 localhost qqq ; \ + status" \ + docker.io/ejabberd/ecs + +... + +:> ejabberdctl register admin localhost asd +User admin@localhost successfully registered + +:> ejabberdctl stats registeredusers +1 + +:> ejabberdctl check_password admin localhost asd + +:> ejabberdctl change_password bot123 localhost qqq +{not_found,"unknown_user"} +:> FAILURE in command 'change_password bot123 localhost qqq' !!! Ignoring result + +:> ejabberdctl status +The node ejabberd@localhost is started. Status: started +ejabberd 25.10.0 is running in that node +``` + + +### Macros in environment + +ejabberd reads `EJABBERD_MACRO_*` environment variables +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. + +For example, if you configure this in `ejabberd.yml`: + +```yaml +acl: + admin: + user: ADMIN +``` + +now you can define the admin account JID using an environment variable: +```yaml + environment: + - EJABBERD_MACRO_ADMIN=admin@localhost ``` -## Use ejabberdapi +Check the [full example](#customized-example) for other example. + + +### ejabberd-contrib + +This section addresses those topics related to +[ejabberd-contrib](https://docs.ejabberd.im/admin/guide/modules/#ejabberd-contrib): + +- [Download source code](#download-source-code) +- [Install a module](#install-a-module) +- [Install git for dependencies](#install-git-for-dependencies) +- [Install your module](#install-your-module) + +--- + +#### Download source code + +The `ejabberd` container image includes the ejabberd-contrib git repository source code, +but `ecs` does not, so first download it: +```bash +$ docker exec ejabberd ejabberdctl modules_update_specs +``` + +#### Install a module + +Compile and install any of the contributed modules, for example: +```bash +docker exec ejabberd ejabberdctl module_install mod_statsdx + +Module mod_statsdx has been installed and started. +It's configured in the file: + /opt/ejabberd/.ejabberd-modules/mod_statsdx/conf/mod_statsdx.yml +Configure the module in that file, or remove it +and configure in your main ejabberd.yml +``` + +#### Install git for dependencies + +Some modules depend on erlang libraries, +but the container images do not include `git` or `mix` to download them. +Consequently, when you attempt to install such a module, +there will be error messages like: + +```bash +docker exec ejabberd ejabberdctl module_install ejabberd_observer_cli + +I'll download "recon" using git because I can't use Mix to fetch from hex.pm: + /bin/sh: mix: not found +Fetching dependency observer_cli: + /bin/sh: git: not found +... +``` + +the solution is to install `git` in the container image: + +```bash +docker exec --user root ejabberd apk add git + +fetch https://dl-cdn.alpinelinux.org/alpine/v3.21/main/x86_64/APKINDEX.tar.gz +fetch https://dl-cdn.alpinelinux.org/alpine/v3.21/community/x86_64/APKINDEX.tar.gz +(1/3) Installing pcre2 (10.43-r0) +(2/3) Installing git (2.47.2-r0) +(3/3) Installing git-init-template (2.47.2-r0) +Executing busybox-1.37.0-r12.trigger +OK: 27 MiB in 42 packages +``` + +and now you can upgrade the module: + +```bash +docker exec ejabberd ejabberdctl module_upgrade ejabberd_observer_cli + +I'll download "recon" using git because I can't use Mix to fetch from hex.pm: +/bin/sh: mix: not found +Fetching dependency observer_cli: Cloning into 'observer_cli'... +Fetching dependency os_stats: Cloning into 'os_stats'... +Fetching dependency recon: Cloning into 'recon'... +Inlining: inline_size=24 inline_effort=150 +Old inliner: threshold=0 functions=[{insert,2},{merge,2}] +Module ejabberd_observer_cli has been installed. +Now you can configure it in your ejabberd.yml +I'll download "recon" using git because I can't use Mix to fetch from hex.pm: +/bin/sh: mix: not found +``` + +#### Install your module + +If you [developed an ejabberd module](https://docs.ejabberd.im/developer/extending-ejabberd/modules/), +you can install it in your container image: + +1. Create a local directory for `ejabberd-modules`: + + ``` sh + mkdir docker-modules + ``` + +2. Then create the directory structure for your custom module: + + ``` sh + cd docker-modules + + mkdir -p sources/mod_hello_world/ + touch sources/mod_hello_world/mod_hello_world.spec + + mkdir sources/mod_hello_world/src/ + mv mod_hello_world.erl sources/mod_hello_world/src/ + + mkdir sources/mod_hello_world/conf/ + echo -e "modules:\n mod_hello_world: {}" > sources/mod_hello_world/conf/mod_hello_world.yml + + cd .. + ``` + +3. Grant ownership of that directory to the UID that ejabberd will use inside the Docker image: + + ``` sh + sudo chown 9000 -R docker-modules/ + ``` + +4. Start ejabberd in the container: + + ``` sh + sudo docker run \ + --name hellotest \ + -d \ + --volume "$(pwd)/docker-modules:/home/ejabberd/.ejabberd-modules/" \ + -p 5222:5222 \ + -p 5280:5280 \ + ejabberd/ecs + ``` + +5. Check the module is available for installing, and then install it: + + ``` sh + sudo docker exec -it hellotest ejabberdctl modules_available + mod_hello_world [] + + sudo docker exec -it hellotest ejabberdctl module_install mod_hello_world + ``` + +6. If the module works correctly, you will see `Hello` in the ejabberd logs when it starts: + + ``` sh + sudo docker exec -it hellotest grep Hello logs/ejabberd.log + 2020-10-06 13:40:13.154335+00:00 [info] + <0.492.0>@mod_hello_world:start/2:15 Hello, ejabberd world! + ``` + + +### 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, this Docker image includes the `ejabberdapi` executable. +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. @@ -164,76 +537,268 @@ api_permissions: Then you could register new accounts with this query: ```bash -docker exec -it ejabberd bin/ejabberdapi register --endpoint=http://127.0.0.1:5282/ --jid=admin@localhost --password=passw0rd +docker exec -it ejabberd ejabberdapi register --endpoint=http://127.0.0.1:5282/ --jid=admin@localhost --password=passw0rd ``` -# Advanced Docker configuration -## Ports +### Clustering -This Docker image exposes the ports: +When setting several containers to form a +[cluster of ejabberd nodes](https://docs.ejabberd.im/admin/guide/clustering/), +each one must have a different +[Erlang Node Name](https://docs.ejabberd.im/admin/guide/security/#erlang-node-name) +and the same +[Erlang Cookie](https://docs.ejabberd.im/admin/guide/security/#erlang-cookie). -- `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. -- `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 +For this you can either: -## Volumes +- edit `conf/ejabberdctl.cfg` and set variables `ERLANG_NODE` and `ERLANG_COOKIE` +- set the environment variables `ERLANG_NODE_ARG` and `ERLANG_COOKIE` -ejabberd produces two types of data: log files and database (Mnesia). -This is the kind of data you probably want to store on a persistent or local drive (at least the database). +--- -Here are the volume you may want to map: +Example to connect a local `ejabberdctl` to a containerized ejabberd: -- `/home/ejabberd/conf/`: Directory containing configuration and certificates -- `/home/ejabberd/database/`: Directory containing Mnesia database. -You should back up or export the content of the directory to persistent storage -(host storage, local storage, any storage plugin) -- `/home/ejabberd/logs/`: Directory containing log files -- `/home/ejabberd/upload/`: Directory containing uploaded files. This should also be backed up. +1. When creating the container, export port 5210, and set `ERLANG_COOKIE`: + ```sh + docker run --name ejabberd -it \ + -e ERLANG_COOKIE=`cat $HOME/.erlang.cookie` \ + -p 5210:5210 -p 5222:5222 \ + docker.io/ejabberd/ecs + ``` +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 -All these files are owned by ejabberd user inside the container. Corresponding -`UID:GID` is `9000:9000`. If you prefer bind mounts instead of docker volumes, then -you need to map this to valid `UID:GID` on your host to get read/write access on -mounted directories. +To connect using a local `ejabberd` script: +```sh +ERL_DIST_PORT=5210 _build/dev/rel/ejabberd/bin/ejabberd ping +``` -## Commands on start +Example using environment variables (see full example [docker-compose.yml](https://github.com/processone/docker-ejabberd/issues/64#issuecomment-887741332)): +```yaml + environment: + - ERLANG_NODE_ARG=ejabberd@node7 + - ERLANG_COOKIE=dummycookie123 +``` -The ejabberdctl script reads the `CTL_ON_CREATE` environment variable -the first time the docker container is started, -and reads `CTL_ON_START` every time the container is started. -Those variables can contain one ejabberdctl command, -or several commands separated with the blankspace and `;` characters. +--- + +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. -Example usage (see the full [docker-compose.yml](https://github.com/processone/docker-ejabberd/issues/64#issuecomment-887741332) example): +Example using environment variables (see the full +[`docker-compose.yml` clustering example](#clustering-example)): ```yaml - environment: - - CTL_ON_CREATE=register admin localhost asd - - CTL_ON_START=stats registeredusers ; - check_password admin localhost asd ; - status +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 \ + docker.io/ejabberd/ecs: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 \ + docker.io/ejabberd/ecs: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 \ + docker.io/ejabberd/ecs:latest +``` + +Check the old database content is available: +```bash +docker exec -it $OLDCONTAINER ejabberdctl registered_users localhost ``` -# Generating ejabberd release +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). -## Configuration -Image is built by embedding an ejabberd Erlang/OTP standalone release in the image. +Build Container Image +---------------- -The configuration of ejabberd Erlang/OTP release is customized with: +The container image includes ejabberd as a standalone OTP release built using Elixir. + +### Build `ejabberd` [![ejabberd Container](https://img.shields.io/badge/ejabberd-grey?logo=opencontainersinitiative&logoColor=2094f3)](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 + +Build ejabberd Community Server container image from ejabberd master git repository: + +```bash +docker buildx build \ + -t personal/ejabberd \ + -f .github/container/Dockerfile \ + . +``` + +#### Podman build + +Some minor remarks: + +- When building, it mentions that `healthcheck` is not supported by the Open Container Initiative image format +- To start with command `live`, you may want to add environment variable `EJABBERD_BYPASS_WARNINGS=true` + +```bash +podman build \ + -t ejabberd \ + -f .github/container/Dockerfile \ + . + +podman run --name eja1 -d -p 5222:5222 localhost/ejabberd + +podman exec eja1 ejabberdctl status + +podman exec -it eja1 sh + +podman stop eja1 + +podman run --name eja1 -it -e EJABBERD_BYPASS_WARNINGS=true -p 5222:5222 localhost/ejabberd live +``` + +### Build `ecs` [![ecs Container](https://img.shields.io/badge/ecs-grey?logo=docker&logoColor=2094f3)](https://hub.docker.com/r/ejabberd/ecs/) + +The ejabberd Erlang/OTP release is configured with: - `rel/config.exs`: Customize ejabberd release - `rel/dev.exs`: ejabberd environment configuration for development release -- `rel/prod.exs`: ejabberd environment configuration for production Docker release +- `rel/prod.exs`: ejabberd environment configuration for production release - `vars.config`: ejabberd compilation configuration options - `conf/ejabberd.yml`: ejabberd default config file Build ejabberd Community Server base image from ejabberd master on Github: ```bash -docker build -t ejabberd/ecs . +docker build -t personal/ejabberd . ``` Build ejabberd Community Server base image for a given ejabberd version: @@ -241,3 +806,331 @@ Build ejabberd Community Server base image for a given ejabberd version: ```bash ./build.sh 18.03 ``` + +Composer Examples +----------------- + +### Minimal Example + +This is the barely minimal file to get a usable ejabberd. + +If using Docker, write this `docker-compose.yml` file +and start it with `docker-compose up`: + +```yaml +services: + main: + image: docker.io/ejabberd/ecs + container_name: ejabberd + ports: + - "5222:5222" + - "5269:5269" + - "5280:5280" + - "5443:5443" +``` + +If using Podman, write this `minimal.yml` file +and start it with `podman kube play minimal.yml`: + +```yaml +apiVersion: v1 + +kind: Pod + +metadata: + name: ejabberd + +spec: + containers: + + - name: ejabberd + image: docker.io/ejabberd/ecs + ports: + - containerPort: 5222 + hostPort: 5222 + - containerPort: 5269 + hostPort: 5269 + - containerPort: 5280 + hostPort: 5280 + - containerPort: 5443 + hostPort: 5443 +``` + + +### Customized Example + +This example shows the usage of several customizations: +it uses a local configuration file, +defines a configuration macro using an environment variable, +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. + +Prepare an ejabberd configuration file: +```bash +mkdir conf && cp ejabberd.yml.example conf/ejabberd.yml +``` + +Create the database directory and allow the container access to it: + +- 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`: + +```yaml +version: '3.7' + +services: + + main: + image: docker.io/ejabberd/ecs + container_name: ejabberd + environment: + - EJABBERD_MACRO_HOST=example.com + - EJABBERD_MACRO_ADMIN=admin@example.com + - REGISTER_ADMIN_PASSWORD=somePassw0rd + - CTL_ON_START=registered_users example.com ; + status + ports: + - "5222:5222" + - "5269:5269" + - "5280:5280" + - "5443:5443" + volumes: + - ./conf/ejabberd.yml:/opt/ejabberd/conf/ejabberd.yml:ro + - ./database:/opt/ejabberd/database +``` + +If using Podman, write this `custom.yml` file +and start it with `podman kube play custom.yml`: + +```yaml +apiVersion: v1 + +kind: Pod + +metadata: + name: ejabberd + +spec: + containers: + + - name: ejabberd + image: docker.io/ejabberd/ecs + env: + - 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 + ports: + - containerPort: 5222 + hostPort: 5222 + - containerPort: 5269 + hostPort: 5269 + - containerPort: 5280 + hostPort: 5280 + - containerPort: 5443 + hostPort: 5443 + volumeMounts: + - mountPath: /opt/ejabberd/conf/ejabberd.yml + name: config + readOnly: true + - mountPath: /opt/ejabberd/database + name: db + + volumes: + - name: config + hostPath: + path: ./conf/ejabberd.yml + type: File + - name: db + hostPath: + path: ./database + type: DirectoryOrCreate +``` + + +### Clustering Example + +In this example, the main container is created first. +Once it is fully started and healthy, a second container is created, +and once ejabberd is started in it, it joins the first one. + +An account is registered in the first node when created (and +we ignore errors that can happen when doing that - for example +when account already exists), +and it should exist in the second node after join. + +Notice that in this example the main container does not have access +to the exterior; the replica exports the ports and can be accessed. + +If using Docker, write this `docker-compose.yml` file +and start it with `docker-compose up`: + +```yaml +version: '3.7' + +services: + + main: + image: docker.io/ejabberd/ecs + 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: docker.io/ejabberd/ecs + container_name: replica + depends_on: + main: + condition: service_healthy + environment: + - ERLANG_NODE_ARG=ejabberd@replica + - ERLANG_COOKIE=dummycookie123 + - CTL_ON_CREATE=join_cluster ejabberd@main + - CTL_ON_START=registered_users localhost ; + status + ports: + - "5222:5222" + - "5269:5269" + - "5280:5280" + - "5443:5443" +``` + +If using Podman, write this `cluster.yml` file +and start it with `podman kube play cluster.yml`: + +```yaml +apiVersion: v1 + +kind: Pod + +metadata: + name: cluster + +spec: + containers: + + - name: first + image: docker.io/ejabberd/ecs + env: + - name: ERLANG_NODE_ARG + value: main@cluster + - name: ERLANG_COOKIE + value: dummycookie123 + - name: CTL_ON_CREATE + value: register admin localhost asd + - name: CTL_ON_START + value: stats registeredusers ; + status + - name: EJABBERD_MACRO_PORT_C2S + value: 6222 + - name: EJABBERD_MACRO_PORT_C2S_TLS + value: 6223 + - name: EJABBERD_MACRO_PORT_S2S + value: 6269 + - name: EJABBERD_MACRO_PORT_HTTP_TLS + value: 6443 + - name: EJABBERD_MACRO_PORT_HTTP + value: 6280 + - name: EJABBERD_MACRO_PORT_MQTT + value: 6883 + - name: EJABBERD_MACRO_PORT_PROXY65 + value: 6777 + volumeMounts: + - mountPath: /opt/ejabberd/conf/ejabberd.yml + name: config + readOnly: true + + - name: second + image: docker.io/ejabberd/ecs + env: + - name: ERLANG_NODE_ARG + value: replica@cluster + - name: ERLANG_COOKIE + value: dummycookie123 + - name: CTL_ON_CREATE + value: join_cluster main@cluster ; + started ; + list_cluster + - name: CTL_ON_START + value: stats registeredusers ; + check_password admin localhost asd ; + status + ports: + - containerPort: 5222 + hostPort: 5222 + - containerPort: 5280 + hostPort: 5280 + volumeMounts: + - mountPath: /opt/ejabberd/conf/ejabberd.yml + name: config + readOnly: true + + volumes: + - name: config + hostPath: + path: ./conf/ejabberd.yml + type: File + +``` + + +Images Comparison +----------------- + +Let's summarize the differences between both container images. Legend: + +- ❇️: is the recommended alternative +- 🟠: changed in ejabberd 26.01 +- 🔆: changed in ... +- 🔅: changed in ejabberd 25.03 + +| | [![ejabberd Container](https://img.shields.io/badge/ejabberd-grey?logo=opencontainersinitiative&logoColor=2094f3)](https://github.com/processone/ejabberd/pkgs/container/ejabberd) | [![ecs Container](https://img.shields.io/badge/ecs-grey?logo=docker&logoColor=2094f3)](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 28.4.1.0-alpine 🟠
Elixir 1.19.5 🟠 | Alpine 3.22
Erlang/OTP 26.2
Elixir 1.18.3 | +| 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](#ejabberd-contrib) | included | not included | +| [ejabberdapi](#ejabberdapi) | included 🔅 | included | +| :black_square_button: **Ports** | +| [1880](#ports) for WebAdmin | yes 🔅 | yes 🔅 | +| [5210](#ports) for `ERL_DIST_PORT` | supported | supported 🔅 | +| :black_square_button: **Paths** | +| `$HOME` | `/opt/ejabberd/` | `/home/ejabberd/` | +| User data | `$HOME` ❇️
`/home/ejabberd/` 🔅 | `$HOME`
`/opt/ejabberd/` ❇️ | +| `ejabberdctl` | `ejabberdctl` ❇️
`bin/ejabberdctl` 🔅 | `bin/ejabberdctl`
`ejabberdctl` ❇️ | +| [`captcha.sh`](#captcha) | `$HOME/bin/captcha.sh` 🔅 | `$HOME/bin/captcha.sh` 🔅 | +| `*.sql` files | `$HOME/sql/*.sql` ❇️ 🔅
`$HOME/database/*.sql` 🔅 | `$HOME/database/*.sql`
`$HOME/sql/*.sql` ❇️ 🔅 | +| Mnesia spool files | `$HOME/database/` ❇️
`$HOME/database/NODENAME/` 🔅 | `$HOME/database/NODENAME/`
`$HOME/database/` ❇️ 🔅 | +| :black_square_button: **Variables** | +| [`EJABBERD_MACRO_*`](#macros-in-environment) | supported | supported | +| Macros used in `ejabberd.yml` | yes 🔅 | yes 🔅 | +| [`EJABBERD_MACRO_ADMIN`](#register-admin-account) | Grant admin rights 🔅
(default `admin@localhost`)
| Hardcoded `admin@localhost` | +| [`REGISTER_ADMIN_PASSWORD`](#register-admin-account) | Register admin account 🔅 | unsupported | +| `CTL_OVER_HTTP` | enabled 🔅 | unsupported | diff --git a/ecs/bin/ejabberdapi b/ecs/bin/ejabberdapi deleted file mode 100755 index 7ae0289..0000000 Binary files a/ecs/bin/ejabberdapi and /dev/null differ diff --git a/ecs/bin/ejabberdctl b/ecs/bin/ejabberdctl index b209057..5512185 100755 --- a/ecs/bin/ejabberdctl +++ b/ecs/bin/ejabberdctl @@ -2,13 +2,12 @@ # define default configuration POLL=true -SMP=enable ERL_MAX_PORTS=32000 ERL_PROCESSES=250000 ERL_MAX_ETS_TABLES=1400 FIREWALL_WINDOW="4370-4379" INET_DIST_INTERFACE="" -ERLANG_NODE=ejabberd@$(hostname -s) +ERLANG_NODE=ejabberd@localhost EJABBERD_BYPASS_WARNINGS=true # define default environment variables @@ -69,8 +68,12 @@ done [ -f "$EJABBERDCTL_CONFIG_PATH" ] && . "$EJABBERDCTL_CONFIG_PATH" [ -n "$ERLANG_NODE_ARG" ] && ERLANG_NODE="$ERLANG_NODE_ARG" [ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && S="-s" +: "${SPOOL_DIR:="$HOME_DIR/database"}" : "${EJABBERD_LOG_PATH:="$LOGS_DIR/ejabberd.log"}" -: "${SPOOL_DIR:="$HOME_DIR/database/$ERLANG_NODE"}" + +# backward support for old mnesia spool dir path +: "${SPOOL_DIR_OLD:="$SPOOL_DIR/$ERLANG_NODE"}" +[ -r "$SPOOL_DIR_OLD/schema.DAT" ] && [ ! -r "$SPOOL_DIR/schema.DAT" ] && SPOOL_DIR="$SPOOL_DIR_OLD" [ -n "$ERLANG_COOKIE" ] && [ ! -f "$HOME"/.erlang.cookie ] && { echo "$ERLANG_COOKIE" > "$HOME"/.erlang.cookie @@ -78,7 +81,7 @@ done } # define erl parameters -ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS" +ERLANG_OPTS="-boot_var RELEASE_LIB ../lib +K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS" if [ -n "$FIREWALL_WINDOW" ] ; then ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}" fi @@ -88,6 +91,7 @@ if [ -n "$INET_DIST_INTERFACE" ] ; then ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface $INET_DIST_INTERFACE2" fi fi +[ -n "$ERL_DIST_PORT" ] && ERLANG_OPTS="$ERLANG_OPTS -erl_epmd_port $ERL_DIST_PORT -start_epmd false" ERL_LIBS="$ROOT_DIR/lib" # if vm.args file exists in config directory, pass it to Erlang VM [ -f "$VMARGS" ] && ERLANG_OPTS="$ERLANG_OPTS -args_file $VMARGS" @@ -95,9 +99,12 @@ ERL_CRASH_DUMP="$LOGS_DIR"/erl_crash_$(date "+%Y%m%d-%H%M%S").dump ERL_INETRC="$ETC_DIR"/inetrc # define ejabberd parameters -EJABBERD_OPTS="$EJABBERD_OPTS\ -$(sed '/^log_rotate_size/!d;s/:[ \t]*\([0-9]\{1,\}\).*/ \1/;s/:[ \t]*\(infinity\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")\ -$(sed '/^log_rotate_count/!d;s/:[ \t]*\([0-9]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")" +EJABBERD_OPTS="\ +$(sed '/^log_rotate_size/!d;s/:[ \t]*\([0-9]\{1,\}\).*/ \1/;s/:[ \t]*\(infinity\).*/ \1 /;s/^/ /' "$EJABBERD_CONFIG_PATH")\ +$(sed '/^log_rotate_count/!d;s/:[ \t]*\([0-9]*\).*/ \1 /;s/^/ /' "$EJABBERD_CONFIG_PATH")\ +$(sed '/^log_burst_limit_count/!d;s/:[ \t]*\([0-9]*\).*/ \1 /;s/^/ /' "$EJABBERD_CONFIG_PATH")\ +$(sed '/^log_burst_limit_window_time/!d;s/:[ \t]*\([0-9]*[a-z]*\).*/ \1 /;s/^/ /' "$EJABBERD_CONFIG_PATH")\ +$EJABBERD_OPTS" [ -n "$EJABBERD_OPTS" ] && EJABBERD_OPTS="-ejabberd $EJABBERD_OPTS" EJABBERD_OPTS="-mnesia dir \"$SPOOL_DIR\" $MNESIA_OPTIONS $EJABBERD_OPTS -s ejabberd" @@ -107,6 +114,7 @@ export EJABBERD_LOG_PATH export EJABBERD_PID_PATH export ERL_CRASH_DUMP export ERL_EPMD_ADDRESS +export ERL_DIST_PORT export ERL_INETRC export ERL_MAX_PORTS export ERL_MAX_ETS_TABLES @@ -114,6 +122,11 @@ export CONTRIB_MODULES_PATH export CONTRIB_MODULES_CONF_DIR export ERL_LIBS +set_dist_client() +{ + [ -n "$ERL_DIST_PORT" ] && ERLANG_OPTS="$ERLANG_OPTS -dist_listen false" +} + # run command either directly or via su $INSTALLUSER run_cmd() { @@ -148,14 +161,6 @@ exec_iex() # usage debugwarning() { - if [ "$OSTYPE" != "cygwin" ] && [ "$OSTYPE" != "win32" ] ; then - if [ "a$TERM" = "a" ] || [ "$TERM" = "dumb" ] ; then - echo "Terminal type not supported." - echo "You may have to set the TERM environment variable to fix this." - exit 8 - fi - fi - if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then echo "--------------------------------------------------------------------" echo "" @@ -166,14 +171,16 @@ debugwarning() echo "Please be extremely cautious with your actions," echo "and exit immediately if you are not completely sure." echo "" - echo "To detach this shell from ejabberd, press:" - echo " control+c, control+c" + echo "To exit and detach this shell from ejabberd, press:" + echo " control+g and then q" echo "" + #vt100 echo "Please do NOT use control+c in this debug shell !" + #vt100 echo "" echo "--------------------------------------------------------------------" echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:" echo " EJABBERD_BYPASS_WARNINGS=true" echo "Press return to continue" - read -r input + read -r _ echo "" fi } @@ -189,28 +196,69 @@ livewarning() echo "Please be extremely cautious with your actions," echo "and exit immediately if you are not completely sure." echo "" - echo "To exit this LIVE mode and stop ejabberd, press:" - echo " q(). and press the Enter key" + echo "To stop ejabberd gracefully:" + echo " ejabberd:stop()." + echo "To quit erlang immediately, press:" + echo " control+g and then q" echo "" echo "--------------------------------------------------------------------" echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:" echo " EJABBERD_BYPASS_WARNINGS=true" echo "Press return to continue" - read -r input + read -r _ echo "" fi } +check_etop_result() +{ + result=$? + if [ $result -eq 1 ] ; then + echo "" + echo "It seems there was some problem running 'ejabberdctl etop'." + echo "Is the error message something like this?" + echo " Failed to load module 'etop' because it cannot be found..." + echo "Then probably ejabberd was compiled with development tools disabled." + echo "To use 'etop', recompile ejabberd with: ./configure --enable-tools" + echo "" + exit $result + fi +} + +check_iex_result() +{ + result=$? + if [ $result -eq 127 ] ; then + echo "" + echo "It seems there was some problem finding 'iex' binary from Elixir." + echo "Probably ejabberd was compiled with Rebar3 and Elixir disabled, like:" + echo " ./configure" + echo "which is equivalent to:" + echo " ./configure --with-rebar=rebar3 --disable-elixir" + echo "To use 'iex', recompile ejabberd enabling Elixir or using Mix:" + echo " ./configure --enable-elixir" + echo " ./configure --with-rebar=mix" + echo "" + exit $result + fi +} + help() { echo "" echo "Commands to start an ejabberd node:" - echo " start Start an ejabberd node in server mode" - echo " debug Attach an interactive Erlang shell to a running ejabberd node" - echo " iexdebug Attach an interactive Elixir shell to a running ejabberd node" - echo " live Start an ejabberd node in live (interactive) mode" - echo " iexlive Start an ejabberd node in live (interactive) mode, within an Elixir shell" - echo " foreground Start an ejabberd node in server mode (attached)" + echo " start Start in server mode" + echo " foreground Start in server mode (attached)" + echo " foreground-quiet Start in server mode (attached), show only critical messages" + echo " live Start in interactive mode, with Erlang shell" + echo " iexlive Start in interactive mode, with Elixir shell" + echo "" + echo "Commands to interact with a running ejabberd node:" + echo " debug Attach an interactive Erlang shell to a running node" + echo " iexdebug Attach an interactive Elixir shell to a running node" + echo " etop Attach to a running node and start Erlang Top" + echo " ping Send ping to the node, returns pong or pang" + echo " started|stopped Wait for the node to fully start|stop" echo "" echo "Optional parameters when starting an ejabberd node:" echo " --config-dir dir Config ejabberd: $ETC_DIR" @@ -237,14 +285,17 @@ uid() # stop epmd if there is no other running node stop_epmd() { + [ -n "$ERL_DIST_PORT" ] && return "$EPMD" -names 2>/dev/null | grep -q name || "$EPMD" -kill >/dev/null } # make sure node not already running and node name unregistered # if all ok, ensure runtime directory exists and make it current directory -# then (docker case) make .sql files available on database volume 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 && { echo "ERROR: The ejabberd node '$ERLANG_NODE' is already running." @@ -258,7 +309,6 @@ check_start() } "$EPMD" -kill >/dev/null } - cp "$HOME_DIR"/lib/ejabberd-*/priv/sql/* "$HOME_DIR/database/" } post_waiter_fork() @@ -278,8 +328,19 @@ post_waiter_loop() LIST=$@ HEAD=${LIST%% ; *} TAIL=${LIST#* ; } - echo ":> ejabberdctl $HEAD" - $0 $HEAD + HEAD2=${HEAD#\! *} + echo ":> ejabberdctl $HEAD2" + $0 $HEAD2 + ctlstatus=$? + if [ $ctlstatus -ne 0 ] ; then + if [ "$HEAD" != "$HEAD2" ] ; then + echo ":> FAILURE in command '$HEAD2' !!! Ignoring result" + else + echo ":> FAILURE in command '$HEAD' !!! Stopping ejabberd..." + $0 halt > /dev/null + exit $ctlstatus + fi + fi [ "$HEAD" = "$TAIL" ] || post_waiter_loop $TAIL } @@ -334,16 +395,19 @@ case $1 in ;; debug) debugwarning + set_dist_client exec_erl "$(uid debug)" -hidden -remsh "$ERLANG_NODE" \ -boot start_clean ;; etop) + set_dist_client exec_erl "$(uid top)" -hidden -node "$ERLANG_NODE" -s etop \ -s erlang halt -output text \ -boot start_clean ;; iexdebug) debugwarning + set_dist_client exec_iex "$(uid debug)" --remsh "$ERLANG_NODE" --boot start_clean ;; iexlive) @@ -353,21 +417,25 @@ case $1 in ping) PEER=${2:-$ERLANG_NODE} [ "$PEER" = "${PEER%.*}" ] && PS="-s" + set_dist_client exec_cmd "$ERL" ${PS:--}name "$(uid ping "$(hostname $PS)")" $ERLANG_OPTS \ -noinput -hidden -eval 'io:format("~p~n",[net_adm:ping('"'$PEER'"')])' \ -s erlang halt -output text \ -boot start_clean ;; started) + set_dist_client wait_status 0 30 2 # wait 30x2s before timeout ;; stopped) + set_dist_client wait_status 3 30 2 && stop_epmd # wait 30x2s before timeout ;; post_waiter) post_waiter_waiting ;; *) + set_dist_client run_erl "$(uid ctl)" -hidden -noinput -boot start_clean \ -s ejabberd_ctl -extra "$ERLANG_NODE" $NO_TIMEOUT "$@" result=$? diff --git a/ecs/conf/ejabberd.yml b/ecs/conf/ejabberd.yml index e4603d9..9aeaead 100644 --- a/ecs/conf/ejabberd.yml +++ b/ecs/conf/ejabberd.yml @@ -14,14 +14,26 @@ ### Refer to http://en.wikipedia.org/wiki/YAML for the brief description. ### +define_macro: + HOST: localhost + ADMIN: "admin@localhost" + PORT_C2S: 5222 + PORT_C2S_TLS: 5223 + PORT_S2S: 5269 + PORT_HTTP_TLS: 5443 + PORT_HTTP: 5280 + PORT_BROWSER: 1880 + PORT_STUN: 5478 + PORT_TURN_MIN: 50000 + PORT_TURN_MAX: 50099 + PORT_MQTT: 1883 + PORT_PROXY65: 7777 + STARTTLS_REQUIRED: true + hosts: - - localhost + - HOST -loglevel: 4 -log_rotate_size: 10485760 -log_rotate_date: "" -log_rotate_count: 1 -log_rate_limit: 100 +loglevel: info certfiles: - /home/ejabberd/conf/server.pem @@ -37,39 +49,66 @@ ca_file: "/home/ejabberd/conf/cacert.pem" listen: - - port: 5222 + port: PORT_C2S ip: "::" module: ejabberd_c2s max_stanza_size: 262144 shaper: c2s_shaper access: c2s - starttls_required: true + starttls_required: STARTTLS_REQUIRED - - port: 5269 + 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: 5443 + 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 - "/oauth": ejabberd_oauth + /admin: ejabberd_web_admin + /api: mod_http_api + /bosh: mod_bosh + /captcha: ejabberd_captcha + /upload: mod_http_upload + /oauth: ejabberd_oauth + /websocket: ejabberd_http_ws - - port: 5280 + port: PORT_HTTP ip: "::" module: ejabberd_http request_handlers: - "/admin": ejabberd_web_admin + /admin: ejabberd_web_admin - - port: 1883 + port: PORT_BROWSER + ip: "::" + module: ejabberd_http + request_handlers: + /: ejabberd_web_admin + - + port: PORT_STUN + ip: "::" + transport: udp + module: ejabberd_stun + use_turn: true + turn_min_port: PORT_TURN_MIN + turn_max_port: PORT_TURN_MAX + ## 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 @@ -132,10 +171,9 @@ acl: ip: - 127.0.0.0/8 - ::1/128 - - ::FFFF:127.0.0.1/128 admin: user: - - "admin@localhost" + - ADMIN access_rules: local: @@ -156,22 +194,30 @@ access_rules: api_permissions: "console commands": - from: - - ejabberd_ctl + from: ejabberd_ctl who: all what: "*" - "admin access": + "webadmin commands": + from: ejabberd_web_admin + who: admin + what: "*" + "adhoc commands": + from: mod_adhoc_api + who: admin + what: "*" + "http access": + from: mod_http_api who: access: allow: - acl: loopback - acl: admin + - acl: loopback + - acl: admin oauth: scope: "ejabberd:admin" access: allow: - acl: loopback - acl: admin + - acl: loopback + - acl: admin what: - "*" - "!stop" @@ -184,8 +230,10 @@ api_permissions: - connected_users_number shaper: - normal: 1000 - fast: 50000 + normal: + rate: 3000 + burst_size: 20000 + fast: 100000 shaper_rules: max_user_sessions: 10 @@ -197,14 +245,13 @@ shaper_rules: normal: all s2s_shaper: fast -max_fsm_queue: 10000 - acme: contact: "mailto:example-admin@example.com" ca_url: "https://acme-staging-v02.api.letsencrypt.org/directory" modules: mod_adhoc: {} + mod_adhoc_api: {} mod_admin_extra: {} mod_announce: access: announce @@ -219,7 +266,11 @@ modules: mod_fail2ban: {} mod_http_api: {} mod_http_upload: - put_url: https://@HOST@:5443/upload + put_url: https://@HOST_URL_ENCODE@: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 @@ -227,7 +278,7 @@ modules: ## to configure. Uncomment this when you have SQL configured: ## db_type: sql assume_mam_usage: true - default: never + default: always mod_mqtt: {} mod_muc: access: @@ -239,8 +290,7 @@ modules: access_mam: - allow default_room_options: - allow_subscription: true # enable MucSub - mam: false + mam: true mod_muc_admin: {} mod_offline: access_max_user_messages: max_user_offline_messages @@ -250,6 +300,7 @@ modules: mod_proxy65: access: local max_connections: 5 + port: PORT_PROXY65 mod_pubsub: access_createnode: pubsub_createnode plugins: @@ -271,10 +322,12 @@ modules: mod_roster: versioning: true mod_sip: {} + 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: diff --git a/ecs/conf/ejabberdctl.cfg b/ecs/conf/ejabberdctl.cfg index 45e8838..907b5a9 100644 --- a/ecs/conf/ejabberdctl.cfg +++ b/ecs/conf/ejabberdctl.cfg @@ -12,42 +12,17 @@ # #POLL=true -#. -#' SMP: SMP support ([enable|auto|disable]) -# -# Explanation in Erlang/OTP documentation: -# enable: starts the Erlang runtime system with SMP support enabled. -# This may fail if no runtime system with SMP support is available. -# auto: starts the Erlang runtime system with SMP support enabled if it -# is available and more than one logical processor are detected. -# disable: starts a runtime system without SMP support. -# -# Default: enable -# -#SMP=enable - #. #' ERL_MAX_PORTS: Maximum number of simultaneously open Erlang ports # # ejabberd consumes two or three ports for every connection, either -# from a client or from another Jabber server. So take this into +# from a client or from another XMPP server. So take this into # account when setting this limit. # -# Default: 32000 +# Default: 65536 (or 8196 on Windows) # Maximum: 268435456 # -#ERL_MAX_PORTS=32000 - -#. -#' FIREWALL_WINDOW: Range of allowed ports to pass through a firewall -# -# If Ejabberd is configured to run in cluster, and a firewall is blocking ports, -# it's possible to make Erlang use a defined range of port (instead of dynamic -# ports) for node communication. -# -# Default: 4370-4379 -# -#FIREWALL_WINDOW= +#ERL_MAX_PORTS=65536 #. #' INET_DIST_INTERFACE: IP address where this Erlang node listens other nodes @@ -60,12 +35,40 @@ #INET_DIST_INTERFACE=127.0.0.1 #. -#' ERL_EPMD_ADDRESS: IP addresses where epmd listens for connections +#' ERL_DIST_PORT: Port number for Erlang distribution # -# IMPORTANT: This option works only in Erlang/OTP R14B03 and newer. +# For Erlang distribution, clustering and ejabberdctl usage, the +# Erlang VM listens in a random TCP port number, and the Erlang Port +# Mapper Daemon (EPMD) is spawned and used to determine this port +# number. +# +# ERL_DIST_PORT can define this port number. In that case, EPMD is +# not spawned during ejabberd startup, and ERL_EPMD_ADDRESS is +# ignored. ERL_DIST_PORT must be set to the same port number during +# ejabberd startup and when calling ejabberdctl. This feature +# requires at least Erlang/OTP 23.1. +# +# Default: not defined +# +#ERL_DIST_PORT=5210 + +#. +#' FIREWALL_WINDOW: Range of allowed ports to pass through a firewall +# +# If ejabberd is configured to run in cluster, and a firewall is blocking ports, +# it's possible to make Erlang use a defined range of port (instead of dynamic +# ports) for node communication. +# +# Default: not defined +# Example: 4200-4210 +# +#FIREWALL_WINDOW= + +#. +#' ERL_EPMD_ADDRESS: IP addresses where EPMD listens for connections # # This environment variable may be set to a comma-separated -# list of IP addresses, in which case the epmd daemon +# list of IP addresses, in which case the EPMD daemon # will listen only on the specified address(es) and on the # loopback address (which is implicitly added to the list if it # has not been specified). The default behaviour is to listen on @@ -84,10 +87,10 @@ # Erlang, and therefore not related to the operating system processes, you do # not have to worry about allowing a huge number of them. # -# Default: 250000 +# Default: 262144 # Maximum: 268435456 # -#ERL_PROCESSES=250000 +#ERL_PROCESSES=262144 #. #' ERL_MAX_ETS_TABLES: Maximum number of ETS and Mnesia tables @@ -98,21 +101,39 @@ # You can safely increase this limit when starting ejabberd. It impacts memory # consumption but the difference will be quite small. # -# Default: 1400 +# Default: 2053 # -#ERL_MAX_ETS_TABLES=1400 +#ERL_MAX_ETS_TABLES=2053 #. #' ERL_OPTIONS: Additional Erlang options # +# The next variable allows to specify additional options passed to +# all commands using erlang interpreter. This applies to starting +# ejabberd server itself but also auxiliary commands like for example +# starting debug shell. See erl(1) for list of commands that can be +# used here. +# +# It might be useful to add "-pa /usr/local/lib/ejabberd/ebin" if you +# want to add local modules in this path. +# +# Default: "" +# +#ERL_OPTIONS="" + +#. +#' EJABBERD_OPTS: Additional Erlang options to start ejabberd +# # The next variable allows to specify additional options passed to erlang while # starting ejabberd. Some useful options are -noshell, -detached, -heart. When # ejabberd is started from an init.d script options -noshell and -detached are # added implicitly. See erl(1) for more info. # +# For example you can use value "-heart -env HEART_BEAT_TIMEOUT 120 -env ERL_CRASH_DUMP_SECONDS 60" +# # Default: "" # -#ERL_OPTIONS="" +#EJABBERD_OPTS="" #. #' ERLANG_NODE: Erlang node name @@ -130,7 +151,7 @@ # # Default: ejabberd@localhost # -#ERLANG_NODE=ejabberd@$(hostname) +#ERLANG_NODE=ejabberd@$(hostname -s) #. #' ERLANG_COOKIE: Erlang cookie for inter-node communication @@ -157,6 +178,17 @@ # #EJABBERD_PID_PATH=/var/run/ejabberd/ejabberd.pid +#. +#' EJABBERD_CONFIG_PATH: ejabberd configuration file +# +# Specify the full path to the ejabberd configuration file. If the file name has +# yml or yaml extension, it is parsed as a YAML file; otherwise, Erlang syntax is +# expected. +# +# Default: $ETC_DIR/ejabberd.yml +# +#EJABBERD_CONFIG_PATH=/etc/ejabberd/ejabberd.yml + #. #' CONTRIB_MODULES_PATH: contributed ejabberd modules path # diff --git a/ecs/config.exs b/ecs/config.exs index ef479a4..d07a643 100644 --- a/ecs/config.exs +++ b/ecs/config.exs @@ -1,4 +1,4 @@ -use Mix.Config +import Config # This is standard path in the context of ejabberd release config :ejabberd, diff --git a/ecs/rel/dev.exs b/ecs/rel/dev.exs index e36c985..a4c910f 100644 --- a/ecs/rel/dev.exs +++ b/ecs/rel/dev.exs @@ -1,4 +1,4 @@ -use Mix.Config +import Config # This is standard path in the context of ejabberd release config :ejabberd, diff --git a/ecs/rel/prod.exs b/ecs/rel/prod.exs index e36c985..a4c910f 100644 --- a/ecs/rel/prod.exs +++ b/ecs/rel/prod.exs @@ -1,4 +1,4 @@ -use Mix.Config +import Config # This is standard path in the context of ejabberd release config :ejabberd, diff --git a/ecs/vars.config b/ecs/vars.config index 4bb31fd..ef48efd 100644 --- a/ecs/vars.config +++ b/ecs/vars.config @@ -1,10 +1,16 @@ +{tools, false}. + {mysql, true}. {odbc, true}. +{mssql, false}. {pgsql, true}. {sqlite, true}. {redis, true}. +{pam, false}. {zlib, true}. {elixir, true}. -{iconv, true}. {stun, true}. {sip, true}. +{lua, true}. + +{release_dir, "${SCRIPT_DIR%/*}"}. diff --git a/mix/Dockerfile b/mix/Dockerfile index b684155..71800f3 100644 --- a/mix/Dockerfile +++ b/mix/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.11 +FROM alpine:3.22 LABEL maintainer="ProcessOne " \ product="Ejabberd mix development environment" @@ -6,9 +6,7 @@ LABEL maintainer="ProcessOne " \ RUN apk upgrade --update musl \ && apk add build-base git zlib-dev openssl-dev yaml-dev expat-dev sqlite-dev \ gd-dev jpeg-dev libpng-dev libwebp-dev autoconf automake bash \ - elixir erlang-crypto erlang-eunit erlang-mnesia erlang-erts erlang-hipe \ - erlang-tools erlang-os-mon erlang-syntax-tools erlang-parsetools \ - erlang-runtime-tools erlang-reltool erlang-odbc file curl \ + elixir erlang-reltool erlang-odbc file curl \ && rm -rf /var/cache/apk/* # Setup runtime environment diff --git a/mix/README.md b/mix/README.md index 73aa336..fa79486 100644 --- a/mix/README.md +++ b/mix/README.md @@ -6,13 +6,13 @@ [![Build Status](https://github.com/processone/docker-ejabberd/actions/workflows/tests.yml/badge.svg)](https://github.com/processone/docker-ejabberd/actions/workflows/tests.yml) [![GitHub stars](https://img.shields.io/github/stars/processone/docker-ejabberd?style=social)](https://github.com/processone/docker-ejabberd) -## Docker image for ejabberd developers +## Container image for ejabberd developers -Thanks to this image, you can build ejabberd with dependencies provided in Docker image, without the need to install build software (beside Docker) directly on your own machine. +Thanks to this image, you can build ejabberd without the need to install build software (beside Docker or equivalent) directly on your own machine. Please note that this image can likely be reused as is to build other Erlang or Elixir software. -### Building ejabberd from source +### Building ejabberd from source You can build ejabberd from source with all dependencies, with the following commands: