diff --git a/.ci/build_packages/Dockerfile b/.ci/build_packages/Dockerfile new file mode 100644 index 000000000..a950d2a28 --- /dev/null +++ b/.ci/build_packages/Dockerfile @@ -0,0 +1,12 @@ +ARG BUILD_FROM=emqx/build-env:erl22.3-ubuntu20.04 +FROM ${BUILD_FROM} + +ARG EMQX_NAME=emqx + +COPY . /emqx + +WORKDIR /emqx + +RUN make ${EMQX_NAME}-pkg || cat rebar3.crashdump + +RUN /emqx/.ci/build_packages/tests.sh diff --git a/.ci/build_packages/tests.sh b/.ci/build_packages/tests.sh new file mode 100755 index 000000000..8498bd8be --- /dev/null +++ b/.ci/build_packages/tests.sh @@ -0,0 +1,171 @@ +#!/bin/sh +set -x -e -u +export EMQX_NAME=${EMQX_NAME:-"emqx"} +export PACKAGE_PATH="/emqx/_packages/${EMQX_NAME}" +export RELUP_PACKAGE_PATH="/emqx/relup_packages/${EMQX_NAME}" +# export EMQX_NODE_NAME="emqx-on-$(uname -m)@127.0.0.1" +# export EMQX_NODE_COOKIE=$(date +%s%N) + +emqx_prepare(){ + mkdir -p ${PACKAGE_PATH} + + if [ ! -d "/paho-mqtt-testing" ]; then + git clone -b develop-4.0 https://github.com/emqx/paho.mqtt.testing.git /paho-mqtt-testing + fi + pip3 install pytest +} + +emqx_test(){ + cd ${PACKAGE_PATH} + + for var in $(ls $PACKAGE_PATH/${EMQX_NAME}-*);do + case ${var##*.} in + "zip") + packagename=`basename ${PACKAGE_PATH}/${EMQX_NAME}-*.zip` + unzip -q ${PACKAGE_PATH}/$packagename + sed -i "/zone.external.server_keepalive/c zone.external.server_keepalive = 60" ${PACKAGE_PATH}/emqx/etc/emqx.conf + sed -i "/mqtt.max_topic_alias/c mqtt.max_topic_alias = 10" ${PACKAGE_PATH}/emqx/etc/emqx.conf + + if [ ! -z $(echo ${EMQX_DEPS_DEFAULT_VSN#v} | grep -oE "[0-9]+\.[0-9]+(\.[0-9]+)?-(alpha|beta|rc)\.[0-9]") ]; then + if [ ! -d ${PACKAGE_PATH}/emqx/lib/emqx-${EMQX_DEPS_DEFAULT_VSN#v} ] || [ ! -d ${PACKAGE_PATH}/emqx/releases/${EMQX_DEPS_DEFAULT_VSN#v} ] ;then + echo "emqx zip version error" + exit 1 + fi + fi + + echo "running ${packagename} start" + ${PACKAGE_PATH}/emqx/bin/emqx start || tail ${PACKAGE_PATH}/emqx/log/erlang.log.1 + IDLE_TIME=0 + while [ -z "$(${PACKAGE_PATH}/emqx/bin/emqx_ctl status |grep 'is running'|awk '{print $1}')" ] + do + if [ $IDLE_TIME -gt 10 ] + then + echo "emqx running error" + exit 1 + fi + sleep 10 + IDLE_TIME=$((IDLE_TIME+1)) + done + pytest -v /paho-mqtt-testing/interoperability/test_client/V5/test_connect.py::test_basic + ${PACKAGE_PATH}/emqx/bin/emqx stop + echo "running ${packagename} stop" + rm -rf ${PACKAGE_PATH}/emqx + ;; + "deb") + packagename=`basename ${PACKAGE_PATH}/${EMQX_NAME}-*.deb` + dpkg -i ${PACKAGE_PATH}/$packagename + if [ $(dpkg -l |grep emqx |awk '{print $1}') != "ii" ] + then + echo "package install error" + exit 1 + fi + + echo "running ${packagename} start" + running_test + echo "running ${packagename} stop" + + dpkg -r ${EMQX_NAME} + if [ $(dpkg -l |grep emqx |awk '{print $1}') != "rc" ] + then + echo "package remove error" + exit 1 + fi + + dpkg -P ${EMQX_NAME} + if [ ! -z "$(dpkg -l |grep emqx)" ] + then + echo "package uninstall error" + exit 1 + fi + ;; + "rpm") + packagename=`basename ${PACKAGE_PATH}/${EMQX_NAME}-*.rpm` + rpm -ivh ${PACKAGE_PATH}/$packagename + if [ -z $(rpm -q emqx | grep -o emqx) ];then + echo "package install error" + exit 1 + fi + + echo "running ${packagename} start" + running_test + echo "running ${packagename} stop" + + rpm -e ${EMQX_NAME} + if [ "$(rpm -q emqx)" != "package emqx is not installed" ];then + echo "package uninstall error" + exit 1 + fi + ;; + + esac + done +} + +running_test(){ + if [ ! -z $(echo ${EMQX_DEPS_DEFAULT_VSN#v} | grep -oE "[0-9]+\.[0-9]+(\.[0-9]+)?-(alpha|beta|rc)\.[0-9]") ]; then + if [ ! -d /usr/lib/emqx/lib/emqx-${EMQX_DEPS_DEFAULT_VSN#v} ] || [ ! -d /usr/lib/emqx/releases/${EMQX_DEPS_DEFAULT_VSN#v} ];then + echo "emqx package version error" + exit 1 + fi + fi + + sed -i "/zone.external.server_keepalive/c zone.external.server_keepalive = 60" /etc/emqx/emqx.conf + sed -i "/mqtt.max_topic_alias/c mqtt.max_topic_alias = 10" /etc/emqx/emqx.conf + + emqx start || tail /var/log/emqx/erlang.log.1 + IDLE_TIME=0 + while [ -z "$(emqx_ctl status |grep 'is running'|awk '{print $1}')" ] + do + if [ $IDLE_TIME -gt 10 ] + then + echo "emqx running error" + exit 1 + fi + sleep 10 + IDLE_TIME=$((IDLE_TIME+1)) + done + pytest -v /paho-mqtt-testing/interoperability/test_client/V5/test_connect.py::test_basic + emqx stop || kill $(ps -ef |grep emqx | grep beam.smp |awk '{print $2}') + + if [ $(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g') = ubuntu ] \ + || [ $(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g') = debian ] \ + || [ $(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g') = raspbian ];then + service emqx start || tail /var/log/emqx/erlang.log.1 + IDLE_TIME=0 + while [ -z "$(emqx_ctl status |grep 'is running'|awk '{print $1}')" ] + do + if [ $IDLE_TIME -gt 10 ] + then + echo "emqx service error" + exit 1 + fi + sleep 10 + IDLE_TIME=$((IDLE_TIME+1)) + done + service emqx stop + fi +} + +relup_test(){ + if [ -d ${RELUP_PACKAGE_PATH} ];then + cd ${RELUP_PACKAGE_PATH } + + for var in $(ls ${EMQX_NAME}-*-$(uname -m).zip);do + packagename=`basename ${var}` + unzip $packagename + ./emqx/bin/emqx start + ./emqx/bin/emqx_ctl status + ./emqx/bin/emqx versions + cp ${PACKAGE_PATH}/${EMQX_NAME}-*-${EMQX_DEPS_DEFAULT_VSN#v}-$(uname -m).zip ./emqx/releases + ./emqx/bin/emqx install ${EMQX_DEPS_DEFAULT_VSN#v} + [ $(./emqx/bin/emqx versions |grep permanent | grep -oE "[0-9].[0-9].[0-9]") = ${EMQX_DEPS_DEFAULT_VSN#v} ] || exit 1 + ./emqx/bin/emqx_ctl status + ./emqx/bin/emqx stop + rm -rf emqx + done + fi +} + +emqx_prepare +emqx_test +relup_test diff --git a/.ci/build_packages/upload_github_release_asset.sh b/.ci/build_packages/upload_github_release_asset.sh new file mode 100755 index 000000000..42dc7e4ef --- /dev/null +++ b/.ci/build_packages/upload_github_release_asset.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# +# Author: Stefan Buck +# License: MIT +# https://gist.github.com/stefanbuck/ce788fee19ab6eb0b4447a85fc99f447 +# +# +# This script accepts the following parameters: +# +# * owner +# * repo +# * tag +# * filename +# * github_api_token +# +# Script to upload a release asset using the GitHub API v3. +# +# Example: +# +# upload-github-release-asset.sh github_api_token=TOKEN owner=stefanbuck repo=playground tag=v0.1.0 filename=./build.zip +# + +# Check dependencies. +set -e +xargs=$(which gxargs || which xargs) + +# Validate settings. +[ "$TRACE" ] && set -x + +CONFIG=$@ + +for line in $CONFIG; do + eval "$line" +done + +# Define variables. +GH_API="https://api.github.com" +GH_REPO="$GH_API/repos/$owner/$repo" +GH_TAGS="$GH_REPO/releases/tags/$tag" +AUTH="Authorization: token $github_api_token" +WGET_ARGS="--content-disposition --auth-no-challenge --no-cookie" +CURL_ARGS="-LJO#" + +if [[ "$tag" == 'LATEST' ]]; then + GH_TAGS="$GH_REPO/releases/latest" +fi + +# Validate token. +curl -o /dev/null -sH "$AUTH" $GH_REPO || { echo "Error: Invalid repo, token or network issue!"; exit 1; } + +# Read asset tags. +response=$(curl -sH "$AUTH" $GH_TAGS) + +# Get ID of the asset based on given filename. +eval $(echo "$response" | grep -m 1 "id.:" | grep -w id | tr : = | tr -cd '[[:alnum:]]=') +[ "$id" ] || { echo "Error: Failed to get release id for tag: $tag"; echo "$response" | awk 'length($0)<100' >&2; exit 1; } + +# Upload asset +# Construct url +GH_ASSET="https://uploads.github.com/repos/$owner/$repo/releases/$id/assets?name=$(basename $filename)" + +curl "$GITHUB_OAUTH_BASIC" --data-binary @"$filename" -H "Authorization: token $github_api_token" -H "Content-Type: application/octet-stream" $GH_ASSET diff --git a/.ci/paho_tests/Makefile b/.ci/paho_tests/Makefile new file mode 100644 index 000000000..022364cc9 --- /dev/null +++ b/.ci/paho_tests/Makefile @@ -0,0 +1,47 @@ +## default globals +TARGET ?= emqx/emqx +EMQX_NAME = $(subst emqx/,,$(TARGET)) + +.PHONY: all +all: test + +define wait_emqx + @while [ "$$(docker inspect -f '{{ .State.Health.Status}}' $$(docker ps -a -q -f name=paho_test_emqx1))" != "healthy" ] || [ "$$(docker inspect -f '{{ .State.Health.Status}}' $$(docker ps -a -q -f name=paho_test_emqx2))" != "healthy" ]; do \ + if [ $$(docker ps -a -f name=paho_test_emqx -f status=exited -q | wc -l) -ne 0 ]; then \ + echo "['$$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:emqx stop"; \ + exit; \ + else \ + echo "['$$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:waiting emqx"; \ + sleep 5; \ + fi; \ + done +endef + +.PHONY: create_container +create_container: clean + @docker-compose -p paho_test up -d + + $(call wait_emqx) + +.PHONY: test +test: create_container + @docker exec -i $$(docker ps -a -q -f name=paho_test_client) sh -c "apk update && apk add git curl \ + && git clone -b $(PAHO_BRANCH) https://github.com/emqx/paho.mqtt.testing.git /paho.mqtt.testing \ + && pip install pytest \ + && pytest -v /paho.mqtt.testing/interoperability/test_client/ --host node1.emqx.io" + + @docker-compose -p paho_test down + +.PHONY: cluster_test +cluster_test: create_container + @docker exec -i $$(docker ps -a -q -f name=paho_test_client) sh -c "apk update && apk add git curl \ + && git clone -b $(PAHO_BRANCH) https://github.com/emqx/paho.mqtt.testing.git /paho.mqtt.testing \ + && pip install pytest \ + && pytest -v /paho.mqtt.testing/interoperability/test_client/V5/test_connect.py -k test_basic --host node1.emqx.io \ + && pytest -v /paho.mqtt.testing/interoperability/test_cluster --host1 node1.emqx.io --host2 node2.emqx.io" + + @docker-compose -p paho_test down + +.PHONY: clean +clean: + @if [ ! -z "$$(docker ps -a -q -f name=paho_test)" ]; then docker-compose -p paho_test down; fi diff --git a/.ci/paho_tests/docker-compose.yaml b/.ci/paho_tests/docker-compose.yaml new file mode 100644 index 000000000..7a3b30478 --- /dev/null +++ b/.ci/paho_tests/docker-compose.yaml @@ -0,0 +1,65 @@ +version: '3' + +services: + emqx1: + image: emqx/emqx:build-alpine-amd64 + environment: + - "EMQX_NAME=emqx" + - "EMQX_HOST=node1.emqx.io" + - "EMQX_CLUSTER__DISCOVERY=static" + - "EMQX_CLUSTER__STATIC__SEEDS=emqx@node1.emqx.io, emqx@node2.emqx.io" + - "EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s" + - "EMQX_MQTT__MAX_TOPIC_ALIAS=10" + command: + - /bin/sh + - -c + - | + sed -i "s 127.0.0.1 $$(ip route show |grep "link" |awk '{print $$1}') g" /opt/emqx/etc/acl.conf + /usr/bin/start.sh + healthcheck: + test: ["CMD", "/opt/emqx/bin/emqx_ctl", "status"] + interval: 5s + timeout: 25s + retries: 5 + networks: + emqx-bridge: + aliases: + - node1.emqx.io + + emqx2: + image: emqx/emqx:build-alpine-amd64 + environment: + - "EMQX_NAME=emqx" + - "EMQX_HOST=node2.emqx.io" + - "EMQX_CLUSTER__DISCOVERY=static" + - "EMQX_CLUSTER__STATIC__SEEDS=emqx@node1.emqx.io, emqx@node2.emqx.io" + - "EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s" + - "EMQX_MQTT__MAX_TOPIC_ALIAS=10" + command: + - /bin/sh + - -c + - | + sed -i "s 127.0.0.1 $$(ip route show |grep "link" |awk '{print $$1}') g" /opt/emqx/etc/acl.conf + /usr/bin/start.sh + healthcheck: + test: ["CMD", "/opt/emqx/bin/emqx_ctl", "status"] + interval: 5s + timeout: 25s + retries: 5 + networks: + emqx-bridge: + aliases: + - node2.emqx.io + + client: + image: python:3.7.2-alpine3.9 + depends_on: + - emqx1 + - emqx2 + tty: true + networks: + emqx-bridge: + +networks: + emqx-bridge: + driver: bridge diff --git a/.github/workflows/build_cross_packages.yaml b/.github/workflows/build_cross_packages.yaml new file mode 100644 index 000000000..c380408db --- /dev/null +++ b/.github/workflows/build_cross_packages.yaml @@ -0,0 +1,393 @@ +name: Cross build packages + +on: + pull_request: + push: + tags: + - v* + +jobs: + windows: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v1 + - uses: ilammy/msvc-dev-cmd@v1 + - uses: gleam-lang/setup-erlang@v1.1.0 + id: install_erlang + with: + otp-version: 22.3 + - name: build + run: | + # set-executionpolicy remotesigned -s cu + # iex (new-object net.webclient).downloadstring('https://get.scoop.sh') + # # $env:path + ";" + $env:USERPROFILE + "\scoop\shims" + ';C:\Program Files\erl10.4\bin' + # [environment]::SetEnvironmentvariable("Path", ";" + $env:USERPROFILE + "\scoop\shims") + # [environment]::SetEnvironmentvariable("Path", ';C:\Program Files\erl10.4\bin') + # scoop bucket add extras https://github.com/lukesampson/scoop-extras.git + # scoop update + # scoop install sudo curl vcredist2013 + + $env:PATH = "${{ steps.install_erlang.outputs.erlpath }}\bin;$env:PATH" + + $version = $( "${{ github.ref }}" -replace "^(.*)/(.*)/" ) + if ($version -match "^v[0-9]+\.[0-9]+(\.[0-9]+)?") { + $regex = "[0-9]+\.[0-9]+(-alpha|-beta|-rc)?\.[0-9]" + $pkg_name = "emqx-windows-$([regex]::matches($version, $regex).value).zip" + } + else { + $pkg_name = "emqx-windows-$($version -replace '/').zip" + } + + make deps-emqx || cat rebar3.crashdump + $rebar3 = $env:USERPROFILE + "\rebar3" + (New-Object System.Net.WebClient).DownloadFile('https://s3.amazonaws.com/rebar3/rebar3', $rebar3) + cd _build/emqx/lib/jiffy/ + escript $rebar3 compile + cd ../../../../ + + make emqx + mkdir -p _packages/emqx + Compress-Archive -Path _build/emqx/rel/emqx -DestinationPath _build/emqx/rel/$pkg_name + mv _build/emqx/rel/$pkg_name _packages/emqx + Get-FileHash -Path "_packages/emqx/$pkg_name" | Format-List | grep 'Hash' | awk '{print $3}' > _packages/emqx/$pkg_name.sha256 + - name: run emqx + run: | + ./_build/emqx/rel/emqx/bin/emqx start + ./_build/emqx/rel/emqx/bin/emqx stop + ./_build/emqx/rel/emqx/bin/emqx install + ./_build/emqx/rel/emqx/bin/emqx uninstall + - uses: actions/upload-artifact@v1 + with: + name: emqx + path: ./_packages/emqx/. + + mac: + runs-on: macos-10.15 + + steps: + - uses: actions/checkout@v1 + - name: prepare + run: | + brew install curl zip unzip gnu-sed kerl unixodbc freetds + echo "/usr/local/bin" >> $GITHUB_PATH + git config --global credential.helper store + - name: build erlang + run: | + kerl build 22.3.4.13 + kerl install 22.3.4.13 $HOME/.kerl/22.3.4.13 + - name: build + run: | + . $HOME/.kerl/22.3.4.13/activate + make emqx-pkg + - name: test + run: | + pkg_name=$(basename _packages/emqx/emqx-macos-*.zip) + unzip _packages/emqx/$pkg_name + ./emqx/bin/emqx start || cat emqx/log/erlang.log.1 + ./emqx/bin/emqx_ctl status + ./emqx/bin/emqx stop + rm -rf emqx + openssl dgst -sha256 ./_packages/emqx/$pkg_name | awk '{print $2}' > ./_packages/emqx/$pkg_name.sha256 + - uses: actions/upload-artifact@v1 + with: + name: emqx + path: ./_packages/emqx/. + + linux: + runs-on: ubuntu-20.04 + + strategy: + matrix: + os: + - ubuntu20.04 + - ubuntu18.04 + - ubuntu16.04 + - debian10 + - debian9 + - opensuse + - centos8 + - centos7 + - centos6 + - raspbian10 + - raspbian9 + + steps: + - name: prepare docker + run: | + mkdir -p $HOME/.docker + echo '{ "experimental": "enabled" }' | tee $HOME/.docker/config.json + echo '{ "experimental": true, "storage-driver": "overlay2", "max-concurrent-downloads": 50, "max-concurrent-uploads": 50}' | sudo tee /etc/docker/daemon.json + sudo systemctl restart docker + docker info + docker buildx create --use --name mybuild + docker run --rm --privileged tonistiigi/binfmt --install all + - uses: actions/checkout@v1 + - name: get deps + run: | + docker run -i --rm \ + -e GITHUB_RUN_ID=$GITHUB_RUN_ID \ + -e GITHUB_REF=$GITHUB_REF \ + -v $(pwd):/emqx \ + -w /emqx \ + emqx/build-env:erl22.3-debian10 \ + bash -c "make deps-all" + - name: downloads emqx zip packages + env: + SYSTEM: ${{ matrix.os }} + run: | + set -e -u -x + version=$(echo ${{ github.ref }} | sed -r "s ^refs/heads/|^refs/tags/(.*) \1 g") + if [ "$(echo $version | grep -oE "^[ev0-9]+\.[0-9]+\.[1-9]?")" = "$version" ]; then + tags=$(git tag -l "$(echo $version | grep -oE "^[ev0-9]+\.[0-9]").*" |grep -v "$version") + for tag in ${tags[@]};do + for arch in x86_64 aarch64; do + mkdir -p tmp/relup_packages/emqx + cd tmp/relup_packages/emqx + if [ ! -z "$(echo $(curl -I -m 10 -o /dev/null -s -w %{http_code} https://packages.emqx.io/emqx-ce/v${tag#[e|v]}/emqx-$SYSTEM-${tag#[e|v]}-$arch.zip) | grep -oE "^[23]+")" ];then + wget https://www.emqx.io/downloads/broker/v${tag#[e|v]}/emqx-$SYSTEM-${tag#[e|v]}-$arch.zip + wget https://www.emqx.io/downloads/broker/v${tag#[e|v]}/emqx-$SYSTEM-${tag#[e|v]}-$arch.zip.sha256 + echo "$(cat emqx-$SYSTEM-${tag#[e|v]}-$arch.zip.sha256) emqx-$SYSTEM-${tag#[e|v]}-$arch.zip" | sha256sum -c || exit 1 + fi + cd - + + mkdir -p tmp/relup_packages/emqx-edge + cd tmp/relup_packages/emqx-edge + if [ ! -z "$(echo $(curl -I -m 10 -o /dev/null -s -w %{http_code} https://packages.emqx.io/emqx-edge/v${tag#[e|v]}/emqx-edge-$SYSTEM-${tag#[e|v]}-$arch.zip) | grep -oE "^[23]+")" ];then + wget https://www.emqx.io/downloads/edge/v${tag#[e|v]}/emqx-edge-$SYSTEM-${tag#[e|v]}-$arch.zip + wget https://www.emqx.io/downloads/edge/v${tag#[e|v]}/emqx-edge-$SYSTEM-${tag#[e|v]}-$arch.zip.sha256 + echo "$(cat emqx-edge-$SYSTEM-${tag#[e|v]}-$arch.zip.sha256) emqx-edge-$SYSTEM-${tag#[e|v]}-$arch.zip" | sha256sum -c || exit 1 + fi + cd - + done + done + fi + - name: build emqx packages on amd64 + env: + ERL_OTP: erl22.3 + SYSTEM: ${{ matrix.os }} + if: matrix.os != 'raspbian9' && matrix.os != 'raspbian10' + run: | + set -e -u -x + for EMQX in emqx emqx-edge; do + docker buildx build --no-cache \ + --platform=linux/amd64 \ + -t cross_build_emqx_for_$SYSTEM \ + -f .ci/build_packages/Dockerfile \ + --build-arg BUILD_FROM=emqx/build-env:$ERL_OTP-$SYSTEM \ + --build-arg EMQX_NAME=${EMQX} \ + --output type=tar,dest=/tmp/cross-build-$EMQX-for-$SYSTEM.tar . + + mkdir -p /tmp/packages/$EMQX + tar -xvf /tmp/cross-build-$EMQX-for-$SYSTEM.tar --wildcards emqx/_packages/$EMQX/* + mv emqx/_packages/$EMQX/* /tmp/packages/$EMQX/ + rm -rf /tmp/cross-build-$EMQX-for-$SYSTEM.tar + + docker rm -f $(docker ps -a -q) + docker volume prune -f + done + - name: build emqx packages on arm64 + if: matrix.os != 'centos6' + env: + ERL_OTP: erl22.3 + SYSTEM: ${{ matrix.os }} + run: | + set -e -u -x + for EMQX in emqx emqx-edge; do + if [ "$EMQX" == "emqx" ] && [ ! -z $( echo $SYSTEM | grep -o raspbian) ];then + continue + fi + + docker buildx build --no-cache \ + --platform=linux/arm64 \ + -t cross_build_emqx_for_$SYSTEM \ + -f .ci/build_packages/Dockerfile \ + --build-arg BUILD_FROM=emqx/build-env:$ERL_OTP-$SYSTEM \ + --build-arg EMQX_NAME=${EMQX} \ + --output type=tar,dest=/tmp/cross-build-$EMQX-for-$SYSTEM.tar . || df -h + + mkdir -p /tmp/packages/$EMQX + tar -xvf /tmp/cross-build-$EMQX-for-$SYSTEM.tar --wildcards emqx/_packages/$EMQX/* + mv emqx/_packages/$EMQX/* /tmp/packages/$EMQX/ + rm -rf /tmp/cross-build-$EMQX-for-$SYSTEM.tar + + docker rm -f $(docker ps -a -q) + docker volume prune -f + done + - name: create sha256 + run: | + if [ -d /tmp/packages/emqx ]; then + cd /tmp/packages/emqx + for var in $(ls emqx-* ); do + bash -c "echo $(sha256sum $var | awk '{print $1}') > $var.sha256" + done + cd - + fi + if [ -d /tmp/packages/emqx-edge ]; then + cd /tmp/packages/emqx-edge + for var in $(ls emqx-edge-* ); do + bash -c "echo $(sha256sum $var | awk '{print $1}') > $var.sha256" + done + cd - + fi + - uses: actions/upload-artifact@v1 + if: matrix.os != 'raspbian9' && matrix.os != 'raspbian10' + with: + name: emqx + path: /tmp/packages/emqx/. + - uses: actions/upload-artifact@v1 + with: + name: emqx-edge + path: /tmp/packages/emqx-edge/. + + docker: + runs-on: ubuntu-20.04 + + strategy: + matrix: + arch: + - [amd64, x86_64] + - [arm64v8, aarch64] + - [arm32v7, arm] + - [i386, i386] + - [s390x, s390x] + + steps: + - uses: actions/checkout@v1 + - name: get deps + run: | + docker run -i --rm \ + -e GITHUB_RUN_ID=$GITHUB_RUN_ID \ + -e GITHUB_REF=$GITHUB_REF \ + -v $(pwd):/emqx \ + -w /emqx \ + emqx/build-env:erl22.3-alpine-amd64 \ + sh -c "make deps-emqx" + - name: build emqx docker image + env: + ARCH: ${{ matrix.arch[0] }} + QEMU_ARCH: ${{ matrix.arch[1] }} + run: | + sudo docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + + sudo TARGET=emqx/emqx ARCH=$ARCH QEMU_ARCH=$QEMU_ARCH make docker + cd _packages/emqx && for var in $(ls emqx-docker-* ); do sudo bash -c "echo $(sha256sum $var | awk '{print $1}') > $var.sha256"; done && cd - + + sudo TARGET=emqx/emqx-edge ARCH=$ARCH QEMU_ARCH=$QEMU_ARCH make docker + cd _packages/emqx-edge && for var in $(ls emqx-edge-docker-* ); do sudo bash -c "echo $(sha256sum $var | awk '{print $1}') > $var.sha256"; done && cd - + - name: docker test + if: matrix.arch[0] == 'amd64' + run: | + sudo TARGET=emqx/emqx PAHO_BRANCH="develop-4.0" make -C .ci/paho_tests cluster_test + sudo TARGET=emqx/emqx-edge PAHO_BRANCH="develop-4.0" make -C .ci/paho_tests cluster_test + - uses: actions/upload-artifact@v1 + with: + name: emqx + path: ./_packages/emqx/. + - uses: actions/upload-artifact@v1 + with: + name: emqx-edge + path: ./_packages/emqx-edge/. + + upload: + runs-on: ubuntu-20.04 + + needs: [windows, mac, linux, docker] + + + steps: + - uses: actions/download-artifact@v2 + with: + name: emqx + path: ./_packages/emqx + - uses: actions/download-artifact@v2 + with: + name: emqx-edge + path: ./_packages/emqx-edge + - name: install dos2unix + run: sudo apt-get update && sudo apt install -y dos2unix + - name: get packages + run: | + set -e -x -u + for EMQX in emqx emqx-edge; do + cd _packages/$EMQX + for var in $( ls |grep emqx |grep -v sha256); do + dos2unix $var.sha256 + echo "$(cat $var.sha256) $var" | sha256sum -c || exit 1 + done + cd - + done + - name: upload aws s3 + if: contains(github.ref, 'refs/tags/') + run: | + set -e -x -u + version=$(echo ${{ github.ref }} | sed -r "s ^refs/heads/|^refs/tags/(.*) \1 g") + curl "https://d1vvhvl2y92vvt.cloudfront.net/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" + unzip awscliv2.zip + sudo ./aws/install + aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }} + aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws configure set default.region us-west-2 + + aws s3 cp --recursive _packages/emqx s3://packages.emqx/emqx-ce/$version + aws s3 cp --recursive _packages/emqx-edge s3://packages.emqx/emqx-edge/$version + aws cloudfront create-invalidation --distribution-id E170YEULGLT8XB --paths "/emqx-ce/$version/*,/emqx-edge/$version/*" + + mkdir packages + mv _packages/emqx/* packages + mv _packages/emqx-edge/* packages + - uses: actions/checkout@v2 + with: + path: emqx + - name: update to github and emqx.io + if: contains(github.ref, 'refs/tags/') + run: | + set -e -x -u + version=$(echo ${{ github.ref }} | sed -r "s ^refs/heads/|^refs/tags/(.*) \1 g") + cd packages + for var in $(ls); do + ../emqx/.ci/build_packages/upload_github_release_asset.sh owner=emqx repo=emqx tag=$version filename=$var github_api_token=$(echo ${{ secrets.AccessToken }}) + sleep 1 + done + curl -w %{http_code} \ + --insecure \ + -H "Content-Type: application/json" \ + -H "token: ${{ secrets.EMQX_IO_TOKEN }}" \ + -X POST \ + -d "{\"repo\":\"emqx/emqx\", \"tag\": \"${version}\" }" \ + ${{ secrets.EMQX_IO_RELEASE_API }} + - name: push docker image to docker hub + if: contains(github.ref, 'refs/tags/') + run: | + set -e -x -u + version=$(echo ${{ github.ref }} | sed -r "s ^refs/heads/|^refs/tags/(.*) \1 g") + sudo make -C emqx docker-prepare + cd packages && for var in $(ls |grep docker |grep -v sha256); do unzip $var; sudo docker load < ${var%.*}; rm -f ${var%.*}; done && cd - + echo ${{ secrets.DOCKER_HUB_TOKEN }} |sudo docker login -u ${{ secrets.DOCKER_HUB_USER }} --password-stdin + sudo TARGET=emqx/emqx make -C emqx docker-push + sudo TARGET=emqx/emqx make -C emqx docker-manifest-list + sudo TARGET=emqx/emqx-edge make -C emqx docker-push + sudo TARGET=emqx/emqx-edge make -C emqx docker-manifest-list + - name: update repo.emqx.io + if: contains(github.ref, 'refs/tags/') + run: | + set -e -x -u + version=$(echo ${{ github.ref }} | sed -r "s ^refs/heads/|^refs/tags/(.*) \1 g") + curl \ + -H "Authorization: token ${{ secrets.AccessToken }}" \ + -H "Accept: application/vnd.github.v3+json" \ + -X POST \ + -d "{\"ref\":\"v1.0.0\",\"inputs\":{\"version\": \"${version}\", \"emqx_ce\": \"true\"}}" \ + https://api.github.com/repos/emqx/emqx-ci-helper/actions/workflows/update_repos.yaml/dispatches + - uses: geekyeggo/delete-artifact@v1 + with: + name: emqx + - uses: geekyeggo/delete-artifact@v1 + with: + name: emqx-edge + # - name: update homebrew packages + # run: | + # version=$(echo ${{ github.ref }} | sed -r "s .*/.*/(.*) \1 g") + # if [ ! -z $(echo $version | grep -oE "v[0-9]+\.[0-9]+(\.[0-9]+)?") ] && [ -z $(echo $version | grep -oE "(alpha|beta|rc)\.[0-9]") ]; then + # curl -H "Authorization: token ${{ secrets.AccessToken }}" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" -X POST -d "{\"event_type\":\"update_homebrew\",\"client_payload\":{\"version\": \"$version\"}}" https://api.github.com/repos/emqx/emqx-packages-docker/dispatches + # fi diff --git a/.gitignore b/.gitignore index 90987d7a3..7fad8b7d7 100644 --- a/.gitignore +++ b/.gitignore @@ -36,5 +36,6 @@ _checkouts rebar.config.rendered /rebar3 rebar.lock +.stamp tmp/ _packages diff --git a/Makefile b/Makefile index 6edd45e57..08d74c284 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -REBAR_VERSION = 3.14.3-emqx-2 -REBAR = ./rebar3 -export PKG_VSN ?= $(shell git describe --tags --always) +REBAR_VERSION = 3.14.3-emqx-3 +REBAR = $(CURDIR)/rebar3 +export PKG_VSN ?= $(shell git describe --tags --match '[0-9]*' 2>/dev/null || git describe --always) # comma separated versions export RELUP_BASE_VERSIONS ?= @@ -18,7 +18,7 @@ all: $(REBAR) $(PROFILES) .PHONY: ensure-rebar3 ensure-rebar3: - @./ensure-rebar3.sh $(REBAR_VERSION) + $(CURDIR)/ensure-rebar3.sh $(REBAR_VERSION) $(REBAR): ensure-rebar3 diff --git a/deploy/charts/emqx/Chart.yaml b/deploy/charts/emqx/Chart.yaml new file mode 100644 index 000000000..2891fe34d --- /dev/null +++ b/deploy/charts/emqx/Chart.yaml @@ -0,0 +1,20 @@ +apiVersion: v2 +name: emqx +description: A Helm chart for EMQ X +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +version: v4.1.1 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. +appVersion: v4.1.1 diff --git a/deploy/charts/emqx/README.md b/deploy/charts/emqx/README.md new file mode 100644 index 000000000..1ba1e3266 --- /dev/null +++ b/deploy/charts/emqx/README.md @@ -0,0 +1,77 @@ +# Introduction +This chart bootstraps an emqx deployment on a Kubernetes cluster using the Helm package manager. + +# Prerequisites ++ Kubernetes 1.6+ ++ Helm + +# Installing the Chart +To install the chart with the release name `my-emqx`: + ++ From github + ``` + $ git clone https://github.com/emqx/emqx.git + $ cd emqx/deploy/charts/emqx + $ helm install my-emqx . + ``` + ++ From chart repos + ``` + helm repo add emqx https://repos.emqx.io/charts + helm install my-emqx emqx/emqx + ``` + > If you want to install an unstable version, you need to add `--devel` when you execute the `helm install` command. + +# Uninstalling the Chart +To uninstall/delete the `my-emqx` deployment: +``` +$ helm del my-emqx +``` + +# Configuration +The following table lists the configurable parameters of the emqx chart and their default values. + +| Parameter | Description | Default Value | +| --- | --- | --- | +| `replicaCount` | It is recommended to have odd number of nodes in a cluster, otherwise the emqx cluster cannot be automatically healed in case of net-split. |3| +| `image.repository` | EMQ X Image name |emqx/emqx| +| `image.pullPolicy` | The image pull policy |IfNotPresent| +| `image.pullSecrets ` | The image pull secrets |`[]` (does not add image pull secrets to deployed pods)| +| `persistence.enabled` | Enable EMQX persistence using PVC |false| +| `persistence.storageClass` | Storage class of backing PVC |`nil` (uses alpha storage class annotation)| +| `persistence.existingClaim` | EMQ X data Persistent Volume existing claim name, evaluated as a template |""| +| `persistence.accessMode` | PVC Access Mode for EMQX volume |ReadWriteOnce| +| `persistence.size` | PVC Storage Request for EMQX volume |20Mi| +| `initContainers` | Containers that run before the creation of EMQX containers. They can contain utilities or setup scripts. |`{}`| +| `resources` | CPU/Memory resource requests/limits |{}| +| `nodeSelector` | Node labels for pod assignment |`{}`| +| `tolerations` | Toleration labels for pod assignment |`[]`| +| `affinity` | Map of node/pod affinities |`{}`| +| `service.type` | Kubernetes Service type. |ClusterIP| +| `service.mqtt` | Port for MQTT. |1883| +| `service.mqttssl` | Port for MQTT(SSL). |8883| +| `service.mgmt` | Port for mgmt API. |8081| +| `service.ws` | Port for WebSocket/HTTP. |8083| +| `service.wss` | Port for WSS/HTTPS. |8084| +| `service.dashboard` | Port for dashboard. |18083| +| `service.nodePorts.mqtt` | Kubernetes node port for MQTT. |nil| +| `service.nodePorts.mqttssl` | Kubernetes node port for MQTT(SSL). |nil| +| `service.nodePorts.mgmt` | Kubernetes node port for mgmt API. |nil| +| `service.nodePorts.ws` | Kubernetes node port for WebSocket/HTTP. |nil| +| `service.nodePorts.wss` | Kubernetes node port for WSS/HTTPS. |nil| +| `service.nodePorts.dashboard` | Kubernetes node port for dashboard. |nil| +| `service.loadBalancerIP` | loadBalancerIP for Service | nil | +| `service.loadBalancerSourceRanges` | Address(es) that are allowed when service is LoadBalancer | [] | +| `service.annotations` | Service annotations | {}(evaluated as a template)| +| `ingress.dashboard.enabled` | Enable ingress for EMQX Dashboard | false | +| `ingress.dashboard.path` | Ingress path for EMQX Dashboard | / | +| `ingress.dashboard.hosts` | Ingress hosts for EMQX Mgmt API | dashboard.emqx.local | +| `ingress.dashboard.tls` | Ingress tls for EMQX Mgmt API | [] | +| `ingress.dashboard.annotations` | Ingress annotations for EMQX Mgmt API | {} | +| `ingress.mgmt.enabled` | Enable ingress for EMQX Mgmt API | false | +| `ingress.mgmt.path` | Ingress path for EMQX Mgmt API | / | +| `ingress.mgmt.hosts` | Ingress hosts for EMQX Mgmt API | api.emqx.local | +| `ingress.mgmt.tls` | Ingress tls for EMQX Mgmt API | [] | +| `ingress.mgmt.annotations` | Ingress annotations for EMQX Mgmt API | {} | +| `emqxConfig` | Emqx configuration item, see the [documentation](https://hub.docker.com/r/emqx/emqx) | | +| `emqxAclConfig` | Emqx acl configuration item, see the [documentation](https://docs.emqx.io/broker/latest/en/advanced/acl-file.html) | | diff --git a/deploy/charts/emqx/templates/StatefulSet.yaml b/deploy/charts/emqx/templates/StatefulSet.yaml new file mode 100644 index 000000000..9e25ce7e6 --- /dev/null +++ b/deploy/charts/emqx/templates/StatefulSet.yaml @@ -0,0 +1,150 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "emqx.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "emqx.name" . }} + helm.sh/chart: {{ include "emqx.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + serviceName: {{ include "emqx.fullname" . }}-headless + {{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} + volumeClaimTemplates: + - metadata: + name: emqx-data + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "emqx.name" . }} + helm.sh/chart: {{ include "emqx.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + annotations: + {{- if .Values.persistence.storageClass }} + volume.beta.kubernetes.io/storage-class: {{ .Values.persistence.storageClass | quote }} + {{- else }} + volume.alpha.kubernetes.io/storage-class: default + {{- end }} + spec: + accessModes: + - {{ .Values.persistence.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{- end }} + updateStrategy: + type: RollingUpdate + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "emqx.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ include "emqx.name" . }} + version: {{ .Chart.AppVersion }} + app.kubernetes.io/name: {{ include "emqx.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + spec: + volumes: + - name: emqx-acl + configMap: + name: {{ include "emqx.fullname" . }}-acl + items: + - key: acl.conf + path: acl.conf + {{- if not .Values.persistence.enabled }} + - name: emqx-data + emptyDir: {} + {{- else if .Values.persistence.existingClaim }} + - name: emqx-data + persistentVolumeClaim: + {{- with .Values.persistence.existingClaim }} + claimName: {{ tpl . $ }} + {{- end }} + {{- end }} + {{- if .Values.emqxLicneseSecretName }} + - name: emqx-license + secret: + secretName: {{ .Values.emqxLicneseSecretName }} + {{- end }} + serviceAccountName: {{ include "emqx.fullname" . }} + securityContext: + fsGroup: 1000 + {{- if .Values.initContainers }} + initContainers: +{{ toYaml .Values.initContainers | indent 8 }} + {{- end }} + containers: + - name: emqx + image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.image.pullSecrets }} + imagePullSecrets: + {{- range .Values.image.pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} + ports: + - name: mqtt + containerPort: {{ .Values.emqxConfig.EMQX_LISTENER__TCP__EXTERNAL | default 1883 }} + - name: mqttssl + containerPort: {{ .Values.emqxConfig.EMQX_LISTENER__SSL__EXTERNAL | default 8883 }} + - name: mgmt + containerPort: {{ .Values.emqxConfig.EMQX_MANAGEMENT__LISTENER__HTTP | default 8081 }} + - name: ws + containerPort: {{ .Values.emqxConfig.EMQX_LISTENER__WS__EXTERNAL | default 8083 }} + - name: wss + containerPort: {{ .Values.emqxConfig.EMQX_LISTENER__WSS__EXTERNAL | default 8084 }} + - name: dashboard + containerPort: {{ .Values.emqxConfig.EMQX_DASHBOARD__LISTENER__HTTP | default 18083 }} + - name: ekka + containerPort: 4370 + envFrom: + - configMapRef: + name: {{ include "emqx.fullname" . }}-env + env: + - name: EMQX_NAME + value: {{ .Release.Name }} + - name: EMQX_CLUSTER__K8S__APP_NAME + value: {{ .Release.Name }} + - name: EMQX_CLUSTER__DISCOVERY + value: k8s + - name: EMQX_CLUSTER__K8S__SERVICE_NAME + value: {{ include "emqx.fullname" . }}-headless + - name: EMQX_CLUSTER__K8S__NAMESPACE + value: {{ .Release.Namespace }} + resources: +{{ toYaml .Values.resources | indent 12 }} + volumeMounts: + - name: emqx-data + mountPath: "/opt/emqx/data/mnesia" + - name: emqx-acl + mountPath: "/opt/emqx/etc/acl.conf" + subPath: "acl.conf" + {{ if .Values.emqxLicneseSecretName }} + - name: emqx-license + mountPath: "/opt/emqx/etc/emqx.lic" + subPath: "emqx.lic" + readOnly: true + {{ end }} + readinessProbe: + httpGet: + path: /status + port: {{ .Values.emqxConfig.EMQX_MANAGEMENT__LISTENER__HTTP | default 8081 }} + initialDelaySeconds: 5 + periodSeconds: 5 + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/deploy/charts/emqx/templates/_helpers.tpl b/deploy/charts/emqx/templates/_helpers.tpl new file mode 100644 index 000000000..ad01d5862 --- /dev/null +++ b/deploy/charts/emqx/templates/_helpers.tpl @@ -0,0 +1,32 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "emqx.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "emqx.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "emqx.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/deploy/charts/emqx/templates/configmap.yaml b/deploy/charts/emqx/templates/configmap.yaml new file mode 100644 index 000000000..bf7a1c1e6 --- /dev/null +++ b/deploy/charts/emqx/templates/configmap.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "emqx.fullname" . }}-env + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "emqx.name" . }} + helm.sh/chart: {{ include "emqx.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +data: + {{- range $index, $value := .Values.emqxConfig}} + {{$index}}: "{{ $value }}" + {{- end}} \ No newline at end of file diff --git a/deploy/charts/emqx/templates/configmap_for_acl.yaml b/deploy/charts/emqx/templates/configmap_for_acl.yaml new file mode 100644 index 000000000..23b8ab583 --- /dev/null +++ b/deploy/charts/emqx/templates/configmap_for_acl.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "emqx.fullname" . }}-acl + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "emqx.name" . }} + helm.sh/chart: {{ include "emqx.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +data: + "acl.conf": | + {{ .Values.emqxAclConfig }} \ No newline at end of file diff --git a/deploy/charts/emqx/templates/ingress.yaml b/deploy/charts/emqx/templates/ingress.yaml new file mode 100644 index 000000000..f527e41f4 --- /dev/null +++ b/deploy/charts/emqx/templates/ingress.yaml @@ -0,0 +1,70 @@ +{{- if .Values.ingress.dashboard.enabled -}} +{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ printf "%s-%s" (include "emqx.fullname" .) "dashboard" }} + labels: + app.kubernetes.io/name: {{ include "emqx.name" . }} + helm.sh/chart: {{ include "emqx.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- if .Values.ingress.dashboard.annotations }} + annotations: + {{- toYaml .Values.ingress.dashboard.annotations | nindent 4 }} + {{- end }} +spec: + rules: + {{- range $host := .Values.ingress.dashboard.hosts }} + - host: {{ $host }} + http: + paths: + - path: / + backend: + serviceName: {{ include "emqx.fullname" $ }} + servicePort: {{ $.Values.service.dashboard }} + {{- end -}} + {{- if .Values.ingress.dashboard.tls }} + tls: + {{- toYaml .Values.ingress.dashboard.tls | nindent 4 }} + {{- end }} +--- +{{- end }} +{{- if .Values.ingress.mgmt.enabled -}} +{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ printf "%s-%s" (include "emqx.fullname" .) "mgmt" }} + labels: + app.kubernetes.io/name: {{ include "emqx.name" . }} + helm.sh/chart: {{ include "emqx.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- if .Values.ingress.mgmt.annotations }} + annotations: + {{- toYaml .Values.ingress.mgmt.annotations | nindent 4 }} + {{- end }} +spec: + rules: + {{- range $host := .Values.ingress.mgmt.hosts }} + - host: {{ $host }} + http: + paths: + - path: / + backend: + serviceName: {{ include "emqx.fullname" $ }} + servicePort: {{ $.Values.service.mgmt }} + {{- end -}} + {{- if .Values.ingress.mgmt.tls }} + tls: + {{- toYaml .Values.ingress.mgmt.tls | nindent 4 }} + {{- end }} +--- +{{- end }} \ No newline at end of file diff --git a/deploy/charts/emqx/templates/rbac.yaml b/deploy/charts/emqx/templates/rbac.yaml new file mode 100644 index 000000000..f1536836b --- /dev/null +++ b/deploy/charts/emqx/templates/rbac.yaml @@ -0,0 +1,34 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "emqx.fullname" . }} +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "emqx.fullname" . }} +rules: +- apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - watch + - list +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "emqx.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ include "emqx.fullname" . }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: Role + name: {{ include "emqx.fullname" . }} + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/deploy/charts/emqx/templates/service.yaml b/deploy/charts/emqx/templates/service.yaml new file mode 100644 index 000000000..ad186adf3 --- /dev/null +++ b/deploy/charts/emqx/templates/service.yaml @@ -0,0 +1,130 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "emqx.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "emqx.name" . }} + helm.sh/chart: {{ include "emqx.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + {{- if eq .Values.service.type "LoadBalancer" }} + {{- if .Values.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + {{- if .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: {{- toYaml .Values.service.loadBalancerSourceRanges | nindent 4 }} + {{- end }} + {{- end }} + ports: + - name: mqtt + port: {{ .Values.service.mqtt | default 1883 }} + protocol: TCP + targetPort: mqtt + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePorts.mqtt)) }} + nodePort: {{ .Values.service.nodePorts.mqtt }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + - name: mqttssl + port: {{ .Values.service.mqttssl | default 8883 }} + protocol: TCP + targetPort: mqttssl + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePorts.mqttssl)) }} + nodePort: {{ .Values.service.nodePorts.mqttssl }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + - name: mgmt + port: {{ .Values.service.mgmt | default 8081 }} + protocol: TCP + targetPort: mgmt + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePorts.mgmt)) }} + nodePort: {{ .Values.service.nodePorts.mgmt }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + - name: ws + port: {{ .Values.service.ws | default 8083 }} + protocol: TCP + targetPort: ws + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePorts.ws)) }} + nodePort: {{ .Values.service.nodePorts.ws }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + - name: wss + port: {{ .Values.service.wss | default 8084 }} + protocol: TCP + targetPort: wss + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePorts.wss)) }} + nodePort: {{ .Values.service.nodePorts.wss }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + - name: dashboard + port: {{ .Values.service.dashboard | default 18083 }} + protocol: TCP + targetPort: dashboard + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePorts.dashboard)) }} + nodePort: {{ .Values.service.nodePorts.dashboard }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + selector: + app.kubernetes.io/name: {{ include "emqx.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "emqx.fullname" . }}-headless + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "emqx.name" . }} + helm.sh/chart: {{ include "emqx.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + type: ClusterIP + sessionAffinity: None + clusterIP: None + ports: + - name: mqtt + port: {{ .Values.service.mqtt | default 1883 }} + protocol: TCP + targetPort: mqtt + - name: mqttssl + port: {{ .Values.service.mqttssl | default 8883 }} + protocol: TCP + targetPort: mqttssl + - name: mgmt + port: {{ .Values.service.mgmt | default 8081 }} + protocol: TCP + targetPort: mgmt + - name: ws + port: {{ .Values.service.ws | default 8083 }} + protocol: TCP + targetPort: ws + - name: wss + port: {{ .Values.service.wss | default 8084 }} + protocol: TCP + targetPort: wss + - name: dashboard + port: {{ .Values.service.dashboard | default 18083 }} + protocol: TCP + targetPort: dashboard + - name: ekka + port: 4370 + protocol: TCP + targetPort: ekka + selector: + app.kubernetes.io/name: {{ include "emqx.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/deploy/charts/emqx/values.yaml b/deploy/charts/emqx/values.yaml new file mode 100644 index 000000000..0c56d9481 --- /dev/null +++ b/deploy/charts/emqx/values.yaml @@ -0,0 +1,158 @@ +## Default values for emqx. +## This is a YAML-formatted file. +## Declare variables to be passed into your templates. + +## It is recommended to have odd number of nodes in a cluster, otherwise the emqx cluster cannot be automatically healed in case of net-split. +replicaCount: 3 +image: + repository: emqx/emqx + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + +persistence: + enabled: false + size: 20Mi + ## If defined, volume.beta.kubernetes.io/storage-class: + ## Default: volume.alpha.kubernetes.io/storage-class: default + # storageClass: "-" + accessMode: ReadWriteOnce + ## Existing PersistentVolumeClaims + ## The value is evaluated as a template + ## So, for example, the name can depend on .Release or .Chart + # existingClaim: "" + +resources: {} + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 500m + # memory: 512Mi + +# Containers that run before the creation of EMQX containers. They can contain utilities or setup scripts. +initContainers: {} + # - name: mysql-probe + # image: alpine + # command: ["sh", "-c", "for i in $(seq 1 300); do nc -zvw1 mysql 3306 && exit 0 || sleep 3; done; exit 1"] + +## EMQX configuration item, see the documentation (https://github.com/emqx/emqx-docker#emq-x-configuration) +emqxConfig: + EMQX_CLUSTER__K8S__APISERVER: "https://kubernetes.default.svc:443" + ## The address type is used to extract host from k8s service. + ## Value: ip | dns | hostname + ## Note:Hostname is only supported after v4.0-rc.2 + EMQX_CLUSTER__K8S__ADDRESS_TYPE: "hostname" + EMQX_CLUSTER__K8S__SUFFIX: "svc.cluster.local" + ## if EMQX_CLUSTER__K8S__ADDRESS_TYPE eq dns + # EMQX_CLUSTER__K8S__SUFFIX: "pod.cluster.local" + +## -------------------------------------------------------------------- +## [ACL](https://docs.emqx.io/broker/latest/en/advanced/acl-file.html) + +## -type(who() :: all | binary() | +## {ipaddr, esockd_access:cidr()} | +## {client, binary()} | +## {user, binary()}). + +## -type(access() :: subscribe | publish | pubsub). + +## -type(topic() :: binary()). + +## -type(rule() :: {allow, all} | +## {allow, who(), access(), list(topic())} | +## {deny, all} | +## {deny, who(), access(), list(topic())}). +## -------------------------------------------------------------------- +emqxAclConfig: > + {allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}. + {allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}. + {deny, all, subscribe, ["$SYS/#", {eq, "#"}]}. + {allow, all}. + +## EMQX Enterprise Edition requires manual creation of a Secret containing the licensed content. Write the name of Secret to the value of "emqxLicneseSecretName" +## Example: +## kubectl create secret generic emqx-license-secret-name --from-file=/path/to/emqx.lic +emqxLicneseSecretName: + +service: + ## Service type + ## + type: ClusterIP + ## Port for MQTT + ## + mqtt: 1883 + ## Port for MQTT(SSL) + ## + mqttssl: 8883 + ## Port for mgmt API + ## + mgmt: 8081 + ## Port for WebSocket/HTTP + ## + ws: 8083 + ## Port for WSS/HTTPS + ## + wss: 8084 + ## Port for dashboard + ## + dashboard: 18083 + ## Specify the nodePort(s) value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + nodePorts: + mqtt: + mqttssl: + mgmt: + ws: + wss: + dashboard: + ## Set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + # loadBalancerIP: + ## Load Balancer sources + ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## Example: + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] + ## Provide any additional annotations which may be required. Evaluated as a template + ## + annotations: {} + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +ingress: + ## ingress for EMQX Dashboard + dashboard: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + path: / + hosts: + - dashboard.emqx.local + tls: [] + + ## ingress for EMQX Mgmt API + mgmt: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + path: / + hosts: + - api.emqx.local + tls: [] + + diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile new file mode 100644 index 000000000..779e3266e --- /dev/null +++ b/deploy/docker/Dockerfile @@ -0,0 +1,80 @@ +ARG BUILD_FROM=emqx/build-env:erl22.3-alpine-amd64 +ARG RUN_FROM=alpine:3.11 +FROM ${BUILD_FROM} AS builder + +ARG QEMU_ARCH=x86_64 +COPY tmp/qemu-$QEMU_ARCH-stati* /usr/bin/ + +RUN apk add --no-cache \ + git \ + curl \ + gcc \ + g++ \ + make \ + perl \ + ncurses-dev \ + openssl-dev \ + coreutils \ + bsd-compat-headers \ + libc-dev \ + libstdc++ \ + bash + +COPY . /emqx + +ARG PKG_VSN +ARG EMQX_NAME=emqx + +RUN cd /emqx && make $EMQX_NAME + +FROM $RUN_FROM + +# Basic build-time metadata as defined at http://label-schema.org +LABEL org.label-schema.docker.dockerfile="Dockerfile" \ + org.label-schema.license="GNU" \ + org.label-schema.name="emqx" \ + org.label-schema.version=${PKG_VSN} \ + org.label-schema.description="EMQ (Erlang MQTT Broker) is a distributed, massively scalable, highly extensible MQTT messaging broker written in Erlang/OTP." \ + org.label-schema.url="http://emqx.io" \ + org.label-schema.vcs-type="Git" \ + org.label-schema.vcs-url="https://github.com/emqx/emqx-docker" \ + maintainer="Raymond M Mouthaan , Huang Rui , EMQ X Team " + +ARG QEMU_ARCH=x86_64 +ARG EMQX_NAME=emqx + +COPY deploy/docker/docker-entrypoint.sh deploy/docker/start.sh tmp/qemu-$QEMU_ARCH-stati* /usr/bin/ +COPY --from=builder /emqx/_build/$EMQX_NAME/rel/emqx /opt/emqx + +RUN ln -s /opt/emqx/bin/* /usr/local/bin/ +RUN apk add --no-cache curl ncurses-libs openssl sudo libstdc++ bash + +WORKDIR /opt/emqx + +RUN adduser -D -u 1000 emqx \ + && echo "emqx ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers + +RUN chgrp -Rf emqx /opt/emqx && chmod -Rf g+w /opt/emqx \ + && chown -Rf emqx /opt/emqx + +USER emqx + +VOLUME ["/opt/emqx/log", "/opt/emqx/data", "/opt/emqx/lib", "/opt/emqx/etc"] + +# emqx will occupy these port: +# - 1883 port for MQTT +# - 8081 for mgmt API +# - 8083 for WebSocket/HTTP +# - 8084 for WSS/HTTPS +# - 8883 port for MQTT(SSL) +# - 11883 port for internal MQTT/TCP +# - 18083 for dashboard +# - 4369 for port mapping (epmd) +# - 4370 for port mapping +# - 5369 for gen_rpc port mapping +# - 6369 for distributed node +EXPOSE 1883 8081 8083 8084 8883 11883 18083 4369 4370 5369 6369 + +ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"] + +CMD ["/usr/bin/start.sh"] diff --git a/deploy/docker/README.md b/deploy/docker/README.md new file mode 100644 index 000000000..1fb2c4d45 --- /dev/null +++ b/deploy/docker/README.md @@ -0,0 +1,336 @@ +# Quick reference + ++ **Where to get help**: + + https://emqx.io, https://github.com/emqx/emqx-rel, or https://github.com/emqx/emqx + ++ **Where to file issues:** + + https://github.com/emqx/emqx/issues + ++ **Supported architectures** + + `amd64`, `arm64v8`, `arm32v7`, `i386`, `s390x` + + ++ **Supported Docker versions**: + + [the latest release](https://github.com/docker/docker-ce/releases/latest) + +# What is EMQ X + +[EMQ X MQTT broker](https://emqx.io/products/broker) is a fully open source, highly scalable, highly available distributed MQTT messaging broker for IoT, M2M and Mobile applications that can handle tens of millions of concurrent clients. + +Starting from 3.0 release, *EMQ X* broker fully supports MQTT V5.0 protocol specifications and backward compatible with MQTT V3.1 and V3.1.1, as well as other communication protocols such as MQTT-SN, CoAP, LwM2M, WebSocket and STOMP. The 3.0 release of the *EMQ X* broker can scaled to 10+ million concurrent MQTT connections on one cluster. + +# How to use this image + +### Run emqx + +Execute some command under this docker image + +``docker run -d --name emqx emqx/emqx:$(tag)`` + +For example + +``docker run -d --name emqx -p 18083:18083 -p 1883:1883 emqx/emqx:latest`` + +The emqx broker runs as linux user `emqx` in the docker container. + +### Configuration + +Use the environment variable to configure the EMQ X docker container. + +The environment variables which with ``EMQX_`` prefix are mapped to configuration fils. + ++ Prefix ``EMQX_`` is removed ++ All upper case letters is replaced with lower case letters ++ ``__`` is replaced with ``.`` + +Example: + +```bash +EMQX_LISTENER__SSL__EXTERNAL__ACCEPTORS <--> listener.ssl.external.acceptors +EMQX_MQTT__MAX_PACKET_SIZE <--> mqtt.max_packet_size +``` + +Non mapped environment variables: + +```bash +EMQX_NAME +EMQX_HOST +``` + +These environment variables will ignore for configuration file. + +#### EMQ X Configuration + +> NOTE: All EMQ X Configuration in [etc/emqx.conf](https://github.com/emqx/emqx/blob/master/etc/emqx.conf) could config by environment. The following list is just an example, not a complete configuration. + +| Options | Default | Mapped | Description | +| ---------------------------| ------------------ | ------------------------- | ------------------------------------- | +| EMQX_NAME | container name | none | emqx node short name | +| EMQX_HOST | container IP | none | emqx node host, IP or FQDN | +| EMQX_WAIT_TIME | 5 | none | wait time in sec before timeout | +| EMQX_NODE__NAME | EMQX_NAME@EMQX_HOST| node.name | Erlang node name, name@ipaddress/host | +| EMQX_NODE__COOKIE | emqx_dist_cookie | node.cookie | cookie for cluster | +| EMQX_LOG__CONSOLE | console | log.console | log console output method | +| EMQX_ALLOW_ANONYMOUS | true | allow_anonymous | allow mqtt anonymous login | +| EMQX_LISTENER__TCP__EXTERNAL| 1883 | listener.tcp.external | MQTT TCP port | +| EMQX_LISTENER__SSL__EXTERNAL| 8883 | listener.ssl.external | MQTT TCP TLS/SSL port | +| EMQX_LISTENER__WS__EXTERNAL | 8083 | listener.ws.external | HTTP and WebSocket port | +| EMQX_LISTENER__WSS__EXTERNAL| 8084 | listener.wss.external | HTTPS and WSS port | +| EMQX_LISTENER__API__MGMT | 8080 | listener.api.mgmt | MGMT API port | +| EMQX_MQTT__MAX_PACKET_SIZE | 64KB | mqtt.max_packet_size | Max Packet Size Allowed | + +The list is incomplete and may changed with [etc/emqx.conf](https://github.com/emqx/emqx/blob/master/etc/emqx.conf) and plugin configuration files. But the mapping rule is similar. + +If set ``EMQX_NAME`` and ``EMQX_HOST``, and unset ``EMQX_NODE__NAME``, ``EMQX_NODE__NAME=$EMQX_NAME@$EMQX_HOST``. + +For example, set mqtt tcp port to 1883 + +``docker run -d --name emqx -e EMQX_LISTENER__TCP__EXTERNAL=1883 -p 18083:18083 -p 1883:1883 emqx/emqx:latest`` + +#### EMQ Loaded Modules Configuration + +| Oprtions | Default | Description | +| ------------------------ | ------------------ | ------------------------------------- | +| EMQX_LOADED_MODULES | see content below | default modules emqx loaded | + +Default environment variable ``EMQX_LOADED_MODULES``, including + ++ ``emqx_mod_acl_internal`` ++ ``emqx_mod_presence`` + +```bash +# The default EMQX_LOADED_MODULES env +EMQX_LOADED_MODULES="emqx_mod_acl_internal,emqx_mod_acl_internal" +``` + +For example, set ``EMQX_LOADED_MODULES=emqx_mod_delayed,emqx_mod_rewrite`` to load these two modules. + +You can use comma, space or other separator that you want. + +All the modules defined in env ``EMQX_LOADED_MODULES`` will be loaded. + +```bash +EMQX_LOADED_MODULES="emqx_mod_delayed,emqx_mod_rewrite" +EMQX_LOADED_MODULES="emqx_mod_delayed emqx_mod_rewrite" +EMQX_LOADED_MODULES="emqx_mod_delayed | emqx_mod_rewrite" +``` + +#### EMQ Loaded Plugins Configuration + +| Oprtions | Default | Description | +| ------------------------ | ------------------ | ------------------------------------- | +| EMQX_LOADED_PLUGINS | see content below | default plugins emqx loaded | + +Default environment variable ``EMQX_LOADED_PLUGINS``, including + ++ ``emqx_recon`` ++ ``emqx_retainer`` ++ ``emqx_management`` ++ ``emqx_dashboard`` + +```bash +# The default EMQX_LOADED_PLUGINS env +EMQX_LOADED_PLUGINS="emqx_recon,emqx_retainer,emqx_management,emqx_dashboard" +``` + +For example, set ``EMQX_LOADED_PLUGINS= emqx_auth_redis,emqx_auth_mysql`` to load these two plugins. + +You can use comma, space or other separator that you want. + +All the plugins defined in ``EMQX_LOADED_PLUGINS`` will be loaded. + +```bash +EMQX_LOADED_PLUGINS="emqx_auth_redis,emqx_auth_mysql" +EMQX_LOADED_PLUGINS="emqx_auth_redis emqx_auth_mysql" +EMQX_LOADED_PLUGINS="emqx_auth_redis | emqx_auth_mysql" +``` + +#### EMQ X Plugins Configuration + +The environment variables which with ``EMQX_`` prefix are mapped to all emqx plugins' configuration file, ``.`` get replaced by ``__``. + +Example: + +```bash +EMQX_AUTH__REDIS__SERVER <--> auth.redis.server +EMQX_AUTH__REDIS__PASSWORD <--> auth.redis.password +``` + +Don't worry about where to find the configuration file of emqx plugins, this docker image will find and config them automatically using some magic. + +All plugin of emqx project could config in this way, following the environment variables mapping rule above. + +Assume you are using redis auth plugin, for example: + +```bash +#EMQX_AUTH__REDIS__SERVER="redis.at.yourserver" +#EMQX_AUTH__REDIS__PASSWORD="password_for_redis" + +docker run -d --name emqx -p 18083:18083 -p 1883:1883 -p 4369:4369 \ + -e EMQX_LISTENER__TCP__EXTERNAL=1883 \ + -e EMQX_LOADED_PLUGINS="emqx_auth_redis" \ + -e EMQX_AUTH__REDIS__SERVER="your.redis.server:6379" \ + -e EMQX_AUTH__REDIS__PASSWORD="password_for_redis" \ + -e EMQX_AUTH__REDIS__PASSWORD_HASH=plain \ + emqx/emqx:latest +``` + +For numbered configuration options where the number is next to a ``.`` such as: + ++ backend.redis.pool1.server ++ backend.redis.hook.message.publish.1 + +You can configure an arbitrary number of them as long as each has a uniq unber for it's own configuration option: + +```bash +docker run -d --name emqx -p 18083:18083 -p 1883:1883 -p 4369:4369 \ + -e EMQX_BACKEND_REDIS_POOL1__SERVER=127.0.0.1:6379 + [...] + -e EMQX_BACKEND__REDIS__POOL5__SERVER=127.0.0.5:6379 + -e EMQX_BACKEND__REDIS__HOOK_MESSAGE__PUBLISH__1='{"topic": "persistant/topic1", "action": {"function": "on_message_publish"}, "pool": "pool1"}' + -e EMQX_BACKEND__REDIS__HOOK_MESSAGE__PUBLISH__2='{"topic": "persistant/topic2", "action": {"function": "on_message_publish"}, "pool": "pool1"}' + -e EMQX_BACKEND__REDIS__HOOK_MESSAGE__PUBLISH__3='{"topic": "persistant/topic3", "action": {"function": "on_message_publish"}, "pool": "pool1"}' + [...] + -e EMQX_BACKEND__REDIS__HOOK_MESSAGE__PUBLISH__13='{"topic": "persistant/topic13", "action": {"function": "on_message_publish"}, "pool": "pool1"}' + emqx/emqx:latest +``` + +#### Mask Sensitive Configuration + +Use ``MASK_CONFIG_FILTER`` to hide senstive configuration values from leaking to logging system. + +For example, set ``MASK_CONFIG_FILTER="password|token"`` to hide all configuration names containing those keywords. + +By default emqx masks the configuration using following filter `"password|passwd|key|token|secret"`. Setting ``MASK_CONFIG_FILTER`` will be merged with the default filter. + +The configuration should match whole word (after splitting it by '.') with `MASK_CONFIG_FILTER`. You can use commas, spaces or other required separators to separate different words. + +### Cluster + +EMQ X supports a variety of clustering methods, see our [documentation](https://docs.emqx.io/broker/latest/en/advanced/cluster.html#emqx-service-discovery) for details. + +Let's create a static node list cluster from docker-compose. + ++ Create `docker-compose.yaml`: + + ```yaml + version: '3' + + services: + emqx1: + image: emqx/emqx:v4.0.0 + environment: + - "EMQX_NAME=emqx" + - "EMQX_HOST=node1.emqx.io" + - "EMQX_CLUSTER__DISCOVERY=static" + - "EMQX_CLUSTER__STATIC__SEEDS=emqx@node1.emqx.io, emqx@node2.emqx.io" + networks: + emqx-bridge: + aliases: + - node1.emqx.io + + emqx2: + image: emqx/emqx:v4.0.0 + environment: + - "EMQX_NAME=emqx" + - "EMQX_HOST=node2.emqx.io" + - "EMQX_CLUSTER__DISCOVERY=static" + - "EMQX_CLUSTER__STATIC__SEEDS=emqx@node1.emqx.io, emqx@node2.emqx.io" + networks: + emqx-bridge: + aliases: + - node2.emqx.io + + networks: + emqx-bridge: + driver: bridge + + ``` + ++ Start the docker-compose cluster + + ```bash + docker-compose -p my_emqx up -d + ``` + ++ View cluster + + ```bash + $ docker exec -it my_emqx_emqx1_1 sh -c "emqx_ctl cluster status" + Cluster status: #{running_nodes => ['emqx@node1.emqx.io','emqx@node2.emqx.io'], + stopped_nodes => []} + ``` + +### Persistence + +If you want to persist the EMQ X docker container, you need to keep the following directories: + ++ `/opt/emqx/data` ++ `/opt/emqx/etc` ++ `/opt/emqx/log` + +Since data in these folders are partially stored under the `/opt/emqx/data/mnesia/${node_name}`, the user also needs to reuse the same node name to see the previous state. In detail, one needs to specify the two environment variables: `EMQX_NAME` and `EMQX_HOST`, `EMQX_HOST` set as `127.0.0.1` or network alias would be useful. + +In if you use docker-compose, the configuration would look something like this: + +```YAML +volumes: + vol-emqx-data: + name: foo-emqx-data + vol-emqx-etc: + name: foo-emqx-etc + vol-emqx-log: + name: foo-emqx-log + +services: + emqx: + image: emqx/emqx:v4.0.0 + restart: always + environment: + EMQX_NAME: foo_emqx + EMQX_HOST: 127.0.0.1 + volumes: + - vol-emqx-data:/opt/emqx/data + - vol-emqx-etc:/opt/emqx/etc + - vol-emqx-log:/opt/emqx/log +``` + +### Kernel Tuning + +Under linux host machine, the easiest way is [tuning host machine's kernel](https://docs.emqx.io/broker/latest/en/tutorial/turn.html#turning-guide). + +If you want tune linux kernel by docker, you must ensure your docker is latest version (>=1.12). + +```bash + +docker run -d --name emqx -p 18083:18083 -p 1883:1883 -p 4369:4369 \ + --sysctl fs.file-max=2097152 \ + --sysctl fs.nr_open=2097152 \ + --sysctl net.core.somaxconn=32768 \ + --sysctl net.ipv4.tcp_max_syn_backlog=16384 \ + --sysctl net.core.netdev_max_backlog=16384 \ + --sysctl net.ipv4.ip_local_port_range=1000 65535 \ + --sysctl net.core.rmem_default=262144 \ + --sysctl net.core.wmem_default=262144 \ + --sysctl net.core.rmem_max=16777216 \ + --sysctl net.core.wmem_max=16777216 \ + --sysctl net.core.optmem_max=16777216 \ + --sysctl net.ipv4.tcp_rmem=1024 4096 16777216 \ + --sysctl net.ipv4.tcp_wmem=1024 4096 16777216 \ + --sysctl net.ipv4.tcp_max_tw_buckets=1048576 \ + --sysctl net.ipv4.tcp_fin_timeout=15 \ + emqx/emqx:latest + +``` + +> REMEMBER: DO NOT RUN EMQ X DOCKER PRIVILEGED OR MOUNT SYSTEM PROC IN CONTAINER TO TUNE LINUX KERNEL, IT IS UNSAFE. + +### Thanks + ++ [@je-al](https://github.com/emqx/emqx-docker/issues/2) ++ [@RaymondMouthaan](https://github.com/emqx/emqx-docker/pull/91) ++ [@zhongjiewu](https://github.com/emqx/emqx/issues/3427) diff --git a/deploy/docker/docker-entrypoint.sh b/deploy/docker/docker-entrypoint.sh new file mode 100755 index 000000000..29c55bf83 --- /dev/null +++ b/deploy/docker/docker-entrypoint.sh @@ -0,0 +1,205 @@ +#!/bin/bash +## EMQ docker image start script +# Huang Rui +# EMQ X Team + +## Shell setting +if [[ -n "$DEBUG" ]]; then + set -ex +else + set -e +fi + +shopt -s nullglob + +## Local IP address setting + +LOCAL_IP=$(hostname -i | grep -oE '((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])\.){3}(25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])' | head -n 1) + +## EMQ Base settings and plugins setting +# Base settings in /opt/emqx/etc/emqx.conf +# Plugin settings in /opt/emqx/etc/plugins + +_EMQX_HOME='/opt/emqx' + +if [[ -z "$EMQX_NAME" ]]; then + EMQX_NAME="$(hostname)" + export EMQX_NAME +fi + +if [[ -z "$EMQX_HOST" ]]; then + if [[ "$EMQX_CLUSTER__K8S__ADDRESS_TYPE" == "dns" ]] && [[ -n "$EMQX_CLUSTER__K8S__NAMESPACE" ]]; then + EMQX_CLUSTER__K8S__SUFFIX=${EMQX_CLUSTER__K8S__SUFFIX:-"pod.cluster.local"} + EMQX_HOST="${LOCAL_IP//./-}.$EMQX_CLUSTER__K8S__NAMESPACE.$EMQX_CLUSTER__K8S__SUFFIX" + elif [[ "$EMQX_CLUSTER__K8S__ADDRESS_TYPE" == 'hostname' ]] && [[ -n "$EMQX_CLUSTER__K8S__NAMESPACE" ]]; then + EMQX_CLUSTER__K8S__SUFFIX=${EMQX_CLUSTER__K8S__SUFFIX:-'svc.cluster.local'} + EMQX_HOST=$(grep -h "^$LOCAL_IP" /etc/hosts | grep -o "$(hostname).*.$EMQX_CLUSTER__K8S__NAMESPACE.$EMQX_CLUSTER__K8S__SUFFIX") + else + EMQX_HOST="$LOCAL_IP" + fi + export EMQX_HOST +fi + +if [[ -z "$EMQX_WAIT_TIME" ]]; then + export EMQX_WAIT_TIME=5 +fi + +if [[ -z "$EMQX_NODE_NAME" ]]; then + export EMQX_NODE_NAME="$EMQX_NAME@$EMQX_HOST" +fi + +# Set hosts to prevent cluster mode failed + +if [[ -z "$EMQX_NODE__PROCESS_LIMIT" ]]; then + export EMQX_NODE__PROCESS_LIMIT=2097152 +fi + +if [[ -z "$EMQX_NODE__MAX_PORTS" ]]; then + export EMQX_NODE__MAX_PORTS=1048576 +fi + +if [[ -z "$EMQX_NODE__MAX_ETS_TABLES" ]]; then + export EMQX_NODE__MAX_ETS_TABLES=2097152 +fi + +if [[ -z "$EMQX__LOG_CONSOLE" ]]; then + export EMQX__LOG_CONSOLE='console' +fi + +if [[ -z "$EMQX_LISTENER__TCP__EXTERNAL__ACCEPTORS" ]]; then + export EMQX_LISTENER__TCP__EXTERNAL__ACCEPTORS=64 +fi + +if [[ -z "$EMQX_LISTENER__TCP__EXTERNAL__MAX_CLIENTS" ]]; then + export EMQX_LISTENER__TCP__EXTERNAL__MAX_CLIENTS=1000000 +fi + +if [[ -z "$EMQX_LISTENER__SSL__EXTERNAL__ACCEPTORS" ]]; then + export EMQX_LISTENER__SSL__EXTERNAL__ACCEPTORS=32 +fi + +if [[ -z "$EMQX_LISTENER__SSL__EXTERNAL__MAX_CLIENTS" ]]; then + export EMQX_LISTENER__SSL__EXTERNAL__MAX_CLIENTS=500000 +fi + +if [[ -z "$EMQX_LISTENER__WS__EXTERNAL__ACCEPTORS" ]]; then + export EMQX_LISTENER__WS__EXTERNAL__ACCEPTORS=16 +fi + +if [[ -z "$EMQX_LISTENER__WS__EXTERNAL__MAX_CLIENTS" ]]; then + export EMQX_LISTENER__WS__EXTERNAL__MAX_CLIENTS=250000 +fi + +# Fix issue #42 - export env EMQX_DASHBOARD__DEFAULT_USER__PASSWORD to configure +# 'dashboard.default_user.password' in etc/plugins/emqx_dashboard.conf +if [[ -n "$EMQX_ADMIN_PASSWORD" ]]; then + export EMQX_DASHBOARD__DEFAULT_USER__PASSWORD=$EMQX_ADMIN_PASSWORD +fi + +# echo value of $VAR hiding secrets if any +# SYNOPSIS +# echo_value KEY VALUE +echo_value() { + # get MASK_CONFIG + MASK_CONFIG_FILTER="$MASK_CONFIG_FILTER|password|passwd|key|token|secret" + FORMAT_MASK_CONFIG_FILTER=$(echo "$MASK_CONFIG_FILTER" | sed -r -e 's/^[^A-Za-z0-9_]+//' -e 's/[^A-Za-z0-9_]+$//' -e 's/[^A-Za-z0-9_]+/|/g') + local key=$1 + local value=$2 + # check if contains sensitive value + if echo "$key" | grep -iqwE "$FORMAT_MASK_CONFIG_FILTER"; then + echo "$key=***secret***" + else + echo "$key=$value" + fi +} + +# fill config on specific file if the key exists +# SYNOPSIS +# try_fill_config FILE KEY VALUE +try_fill_config() { + local file=$1 + local key=$2 + local value=$3 + local escaped_key + # shellcheck disable=SC2001 + escaped_key=$(echo "$key" | sed 's/[^a-zA-Z0-9_]/\\&/g') + local escaped_value + escaped_value=$(echo "$value" | sed 's/[\/&]/\\&/g') + if grep -qE "^[#[:space:]]*$escaped_key\s*=" "$file"; then + echo_value "$key" "$value" + if [[ -z "$value" ]]; then + sed -i -r "s/^[#[:space:]]*($escaped_key)\s*=\s*(.*)/# \1 = \2/" "$file" + else + sed -i -r "s/^[#[:space:]]*($escaped_key)\s*=\s*(.*)/\1 = $escaped_value/" "$file" + fi + # Check if config has a numbering system, but no existing configuration line in file + elif echo "$key" | grep -qE '\.\d+|\d+\.'; then + if [[ -n "$value" ]]; then + local template + template="$(echo "$escaped_key" | sed -r -e 's/\\\.[0-9]+/\\.[0-9]+/g' -e 's/[0-9]+\\\./[0-9]+\\./g')" + if grep -qE "^[#[:space:]]*$template\s*=" "$file"; then + echo_value "$key" "$value" + sed -i '$a'\\ "$file" + echo "$key = $value" >> "$file" + fi + fi + fi +} + +# Catch all EMQX_ prefix environment variable and match it in configure file +CONFIG_FILE="$_EMQX_HOME/etc/emqx.conf" +CONFIG_PLUGINS="$_EMQX_HOME/etc/plugins" +for VAR in $(compgen -e); do + # Config normal keys such like node.name = emqx@127.0.0.1 + if echo "$VAR" | grep -q '^EMQX_'; then + VAR_NAME=$(echo "$VAR" | sed -e 's/^EMQX_//' -e 's/__/./g' | tr '[:upper:]' '[:lower:]' | tr -d '[:cntrl:]') + VAR_VALUE=$(echo "${!VAR}" | tr -d '[:cntrl:]') + # Config in emqx.conf + try_fill_config "$CONFIG_FILE" "$VAR_NAME" "$VAR_VALUE" + # Config in plugins/* + for CONFIG_PLUGINS_FILE in "$CONFIG_PLUGINS"/*; do + try_fill_config "$CONFIG_PLUGINS_FILE" "$VAR_NAME" "$VAR_VALUE" + done + fi +done + +# fill tuples on specific file +# SYNOPSIS +# fill_tuples FILE [ELEMENTS ...] +fill_tuples() { + local file=$1 + local elements=${*:2} + for var in $elements; do + if grep -qE "\{\s*$var\s*,\s*(true|false)\s*\}\s*\." "$file"; then + sed -i -r "s/\{\s*($var)\s*,\s*(true|false)\s*\}\s*\./{\1, true}./1" "$file" + elif grep -q "$var\s*\." "$file"; then + # backward compatible. + sed -i -r "s/($var)\s*\./{\1, true}./1" "$file" + else + sed -i '$a'\\ "$file" + echo "{$var, true}." >>"$file" + fi + done +} + +## EMQX Plugin load settings +# Plugins loaded by default +LOADED_PLUGINS="$_EMQX_HOME/data/loaded_plugins" +if [[ -n "$EMQX_LOADED_PLUGINS" ]]; then + EMQX_LOADED_PLUGINS=$(echo "$EMQX_LOADED_PLUGINS" | tr -d '[:cntrl:]' | sed -r -e 's/^[^A-Za-z0-9_]+//g' -e 's/[^A-Za-z0-9_]+$//g' -e 's/[^A-Za-z0-9_]+/ /g') + echo "EMQX_LOADED_PLUGINS=$EMQX_LOADED_PLUGINS" + # Parse module names and place `{module_name, true}.` tuples in `loaded_plugins`. + fill_tuples "$LOADED_PLUGINS" "$EMQX_LOADED_PLUGINS" +fi + +## EMQX Modules load settings +# Modules loaded by default +LOADED_MODULES="$_EMQX_HOME/data/loaded_modules" +if [[ -n "$EMQX_LOADED_MODULES" ]]; then + EMQX_LOADED_MODULES=$(echo "$EMQX_LOADED_MODULES" | tr -d '[:cntrl:]' | sed -r -e 's/^[^A-Za-z0-9_]+//g' -e 's/[^A-Za-z0-9_]+$//g' -e 's/[^A-Za-z0-9_]+/ /g') + echo "EMQX_LOADED_MODULES=$EMQX_LOADED_MODULES" + # Parse module names and place `{module_name, true}.` tuples in `loaded_modules`. + fill_tuples "$LOADED_MODULES" "$EMQX_LOADED_MODULES" +fi + +exec "$@" diff --git a/deploy/docker/start.sh b/deploy/docker/start.sh new file mode 100755 index 000000000..e4e590363 --- /dev/null +++ b/deploy/docker/start.sh @@ -0,0 +1,60 @@ +#!/bin/sh +set -e -u + +emqx_exit(){ + # At least erlang.log.1 exists + if [ -f /opt/emqx/log/erlang.log.1 ]; then + # tail emqx.log.* + erlang_log=$(echo $(ls -t /opt/emqx/log/erlang.log.*) | awk '{print $1}') + num=$(sed -n -e '/LOGGING STARTED/=' ${erlang_log} | tail -1) + [ ! -z $num ] && [ $num -gt 2 ] && tail -n +$(expr $num - 2) ${erlang_log} + fi + + echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:emqx exit abnormally" + exit 1 +} + +## EMQ Main script + +# When receiving the EXIT signal, execute emqx_exit function +trap "emqx_exit" EXIT + +# Start and run emqx, and when emqx crashed, this container will stop +/opt/emqx/bin/emqx start + +# Sleep 5 seconds to wait for the loaded plugins catch up. +sleep 5 + +echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:emqx start" + +## Fork tailing erlang.log, the fork is not killed after this script exits +## The assumption is that this is the docker entrypoint, +## hence docker container is terminated after entrypoint exists +tail -f /opt/emqx/log/erlang.log.1 & + +# monitor emqx is running, or the docker must stop to let docker PaaS know +# warning: never use infinite loops such as `` while true; do sleep 1000; done`` here +# you must let user know emqx crashed and stop this container, +# and docker dispatching system can known and restart this container. +IDLE_TIME=0 +MGMT_CONF='/opt/emqx/etc/plugins/emqx_management.conf' +MGMT_PORT=$(sed -n -r '/^management.listener.http[ \t]=[ \t].*$/p' $MGMT_CONF | sed -r 's/^management.listener.http = (.*)$/\1/g') +while [ $IDLE_TIME -lt 5 ]; do + IDLE_TIME=$(expr $IDLE_TIME + 1) + if curl http://localhost:${MGMT_PORT}/status >/dev/null 2>&1; then + IDLE_TIME=0 + # Print the latest erlang.log + now_erlang_log=$(ps -ef |grep "tail -f /opt/emqx/log/erlang.log" |grep -v grep | sed -r "s/.*tail -f (.*)/\1/g") + new_erlang_log="$(ls -t /opt/emqx/log/erlang.log.* | head -1)" + if [ $now_erlang_log != $new_erlang_log ];then + tail -f $new_erlang_log & + kill $(ps -ef |grep "tail -f $now_erlang_log" | grep -v grep | awk '{print $1}') + fi + else + echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:emqx not running, waiting for recovery in $((25-IDLE_TIME*5)) seconds" + fi + sleep 5 +done + +# If running to here (the result 5 times not is running, thus in 25s emqx is not running), exit docker image +# Then the high level PaaS, e.g. docker swarm mode, will know and alert, rebanlance this service diff --git a/deploy/packages/README.md b/deploy/packages/README.md new file mode 100644 index 000000000..cdb3384fb --- /dev/null +++ b/deploy/packages/README.md @@ -0,0 +1,24 @@ +emqx-packages +============= + +EMQ X RPM/Debian Packages + +NOTICE: Requires Erlang/OTP R21+ to build since 3.0 release. + +How to use +---------------------------- + +``` +cd project-root-directory-path +EMQX_DEPS_DEFAULT_VSN=${version} make emqx-pkg +``` + +License +------- + +Apache License Version 2.0 + +Author +------ + +EMQ X Team. diff --git a/deploy/packages/deb/Makefile b/deploy/packages/deb/Makefile new file mode 100644 index 000000000..c7a5ff2a0 --- /dev/null +++ b/deploy/packages/deb/Makefile @@ -0,0 +1,31 @@ +# Keep this short to avoid bloating beam files with long file path info +TOPDIR := /tmp/emqx +SRCDIR := $(TOPDIR)/$(PKG_VSN) +BUILT := $(SRCDIR)/BUILT + +EMQX_NAME=$(subst -pkg,,$(EMQX_BUILD)) + +TAR_PKG := $(EMQX_REL)/_build/$(EMQX_BUILD)/rel/emqx/emqx-$(PKG_VSN).tar.gz +SOURCE_PKG := $(EMQX_NAME)_$(PKG_VSN)_$(shell dpkg --print-architecture) +TARGET_PKG := $(EMQX_NAME)-$(SYSTEM)-$(PKG_VSN)-$(shell uname -m) + +.PHONY: all +all: | $(BUILT) + cp -r debian $(SRCDIR)/ + sed -i "s##$(shell date -u '+%a, %d %b %Y %T %z')#g" $(SRCDIR)/debian/changelog + sed -i "s##$(PKG_VSN)#g" $(SRCDIR)/debian/changelog + if [ ! -z $(shell echo $(EMQX_NAME) |grep edge) ]; then \ + sed -i "s/emqx-pkg/emqx-edge-pkg/g" $(SRCDIR)/debian/rules; \ + sed -i "s debian/emqx debian/emqx-edge g" $(SRCDIR)/debian/rules; \ + sed -i "s/Package: emqx/Package: emqx-edge/1" $(SRCDIR)/debian/control; \ + fi + cd $(SRCDIR) && dpkg-buildpackage -us -uc + mkdir -p $(EMQX_REL)/_packages/$(EMQX_NAME) + cp $(SRCDIR)/../$(SOURCE_PKG).deb $(EMQX_REL)/_packages/$(EMQX_NAME)/$(TARGET_PKG).deb + +$(BUILT): + mkdir -p $(TOPDIR) $(SRCDIR) + tar zxf $(TAR_PKG) -C $(SRCDIR) + +clean: + rm -rf $(SRCDIR) diff --git a/deploy/packages/deb/debian/changelog b/deploy/packages/deb/debian/changelog new file mode 100644 index 000000000..e7b32a16e --- /dev/null +++ b/deploy/packages/deb/debian/changelog @@ -0,0 +1,5 @@ +emqx () unstable; urgency=medium + + * See github commit history: https://github.com/emqx/emqx + + -- emqx diff --git a/deploy/packages/deb/debian/compat b/deploy/packages/deb/debian/compat new file mode 100644 index 000000000..ec635144f --- /dev/null +++ b/deploy/packages/deb/debian/compat @@ -0,0 +1 @@ +9 diff --git a/deploy/packages/deb/debian/control b/deploy/packages/deb/debian/control new file mode 100644 index 000000000..e35535c12 --- /dev/null +++ b/deploy/packages/deb/debian/control @@ -0,0 +1,12 @@ +Source: emqx +Section: unknown +Priority: optional +Maintainer: emqx +Build-Depends: debhelper (>=9) +Standards-Version: 3.9.6 +Homepage: https://www.emqx.io + +Package: emqx +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: EMQX, a distributed, massively scalable, highly extensible MQTT message broker written in Erlang/OTP diff --git a/deploy/packages/deb/debian/copyright b/deploy/packages/deb/debian/copyright new file mode 100644 index 000000000..8f71f43fe --- /dev/null +++ b/deploy/packages/deb/debian/copyright @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/deploy/packages/deb/debian/init.script b/deploy/packages/deb/debian/init.script new file mode 100755 index 000000000..0fcafd1d2 --- /dev/null +++ b/deploy/packages/deb/debian/init.script @@ -0,0 +1,150 @@ +#! /bin/bash +### BEGIN INIT INFO +# Provides: emqx +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Erlang MQTT Broker +# Description: EMQX, a distributed, massively scalable, highly extensible MQTT message broker written in Erlang/OT +### END INIT INFO + +NAME=emqx +DAEMON=/usr/bin/$NAME +SCRIPTNAME=/etc/init.d/$NAME + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh +. /lib/lsb/init-functions + +# `service` strips all environmental VARS so +# if no HOME was set in /etc/default/$NAME then set one here +# to the data directory for erlexec's sake +if [ -z "$HOME" ]; then + export HOME=/var/lib/emqx +fi + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + + # Startup with the appropriate user + start-stop-daemon --start \ + --name emqx \ + --user emqx \ + --exec $DAEMON -- start \ + || return 2 +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Identify the erts directory + ERTS_PATH=`$DAEMON ertspath` + + # Attempt a clean shutdown. + $DAEMON stop + + # waiting stop done sleep 5 + sleep 5 + + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + # Make sure it's down by using a more direct approach + start-stop-daemon --stop \ + --quiet \ + --retry=TERM/30/KILL/5 \ + --user emqx \ + --exec $ERTS_PATH/run_erl + return $? +} + +# +# Function that graceful reload the daemon/service +# +do_reload() { + # Restart the VM without exiting the process + $DAEMON restart && return $? || return 2 +} + +# Checks the status of a node +do_status() { + $DAEMON ping && echo $"$NAME is running" && return 0 + echo $"$NAME is stopped" && return 2 +} + +case "$1" in + start) + log_daemon_msg "Starting $NAME" + $DAEMON ping >/dev/null 2>&1 && echo $"$NAME is already running" && exit 0 + do_start + case "$?" in + 0|1) log_end_msg 0 ;; + 2) log_end_msg 1 + exit 1 + ;; + esac + ;; + stop) + log_daemon_msg "Stopping $NAME" + do_stop + case "$?" in + 0|1) log_end_msg 0 ;; + 2) log_end_msg 1 + exit 1 + ;; + esac + ;; + ping) + # See if the VM is alive + $DAEMON ping || exit $? + ;; + reload|force-reload) + log_daemon_msg "Reloading $NAME" + do_reload + ES=$? + log_end_msg $ES + exit $ES + ;; + restart) + log_daemon_msg "Restarting $NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 && exit 1 ;; # Old process is still running + *) log_end_msg 1 && exit 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 && exit 1 + ;; + esac + ;; + status) + do_status && exit 0 || exit $? + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|ping|restart|force-reload|status}" >&2 + exit 3 + ;; +esac + + diff --git a/deploy/packages/deb/debian/postinst b/deploy/packages/deb/debian/postinst new file mode 100755 index 000000000..3c114db64 --- /dev/null +++ b/deploy/packages/deb/debian/postinst @@ -0,0 +1,59 @@ +#!/bin/sh +# postinst script for emqx +# +# see: dh_installdeb(1) + +set -e + +# install startup script +update-rc.d emqx defaults >/dev/null + +# create group +if ! getent group emqx >/dev/null; then + addgroup --system emqx +fi + +# create user +if ! getent passwd emqx >/dev/null; then + adduser --ingroup emqx \ + --home /var/lib/emqx \ + --disabled-password \ + --system --shell /bin/bash --no-create-home \ + --gecos "emqx user" emqx +fi + +for i in lib run log; do + chown -R emqx:emqx /var/$i/emqx +done + +chown -R emqx:emqx /usr/lib/emqx +chown -R emqx:emqx /etc/emqx + +chmod 0755 /var/run/emqx /etc/emqx +chmod 0644 /etc/emqx/* +chmod -R +X /etc/emqx +chmod -R 0755 /usr/lib/emqx/bin +[ -f /usr/bin/emqx ] && rm /usr/bin/emqx +[ -f /usr/bin/emqx_ctl ] && rm /usr/bin/emqx_ctl +ln -s /usr/lib/emqx/bin/emqx /usr/bin/emqx +ln -s /usr/lib/emqx/bin/emqx_ctl /usr/bin/emqx_ctl + +case "$1" in + configure) + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/deploy/packages/deb/debian/postrm b/deploy/packages/deb/debian/postrm new file mode 100755 index 000000000..9d6072f58 --- /dev/null +++ b/deploy/packages/deb/debian/postrm @@ -0,0 +1,82 @@ +#!/bin/sh +# postrm script for emqx +# +# see: dh_installdeb(1) + + +# summary of how this script can be called: +# * `remove' +# * `purge' +# * `upgrade' +# * `failed-upgrade' +# * `abort-install' +# * `abort-install' +# * `abort-upgrade' +# * `disappear' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +set -e + +case "$1" in + purge) + rm -f /etc/default/emqx + + # ensure we remove the rc.d scripts installed by postinst + update-rc.d emqx remove >/dev/null + + if [ -d /var/lib/emqx ]; then + rm -r /var/lib/emqx + fi + if [ -d /var/log/emqx ]; then + rm -r /var/log/emqx + fi + if [ -d /var/run/emqx ]; then + rm -r /var/run/emqx + fi + if [ -d /etc/emqx ]; then + rm -r /etc/emqx + fi + if [ -e /etc/init.d/emqx ]; then + rm /etc/init.d/emqx + fi + # Remove User & Group, killing any process owned by them + if getent passwd emqx >/dev/null; then + pkill -u emqx || true + deluser --quiet --system emqx + fi + if getent group emqx >/dev/null; then + delgroup --quiet --system --only-if-empty emqx || true + fi + if [ -f /usr/bin/emqx ]; then + rm /usr/bin/emqx + fi + if [ -f /usr/bin/emqx_ctl ]; then + rm /usr/bin/emqx_ctl + fi + if [ -d /usr/lib/emqx ]; then + rm -r /usr/lib/emqx + fi + ;; + + remove) + rm /usr/bin/emqx + rm /usr/bin/emqx_ctl + ;; + + upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + + *) + echo "postrm called with unknown argument \`$1\`" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/deploy/packages/deb/debian/rules b/deploy/packages/deb/debian/rules new file mode 100755 index 000000000..1a9fe98e2 --- /dev/null +++ b/deploy/packages/deb/debian/rules @@ -0,0 +1,65 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# modified for node_package by dizzyd@basho.com and jared@basho.com + +# Uncomment this to turn on verbose mode. +export DH_VERBOSE=1 + + +## Clear variables that may confound our build of sub-projects; also +## note that it is necessary to use overlay_vars relative to .. as +## the generate command EXECUTES in rel/ +build: + +clean: + dh_clean + rm -f build + # make clean + +## dh_shlibdeps was added to figure out the dependencies on shared libraries +## and will populate the ${shlibs:Depends} callout in the control file +install: build + dh_testdir + dh_testroot + dh_prep + dh_installdirs + mkdir -p debian/emqx/usr/lib/emqx + mkdir -p debian/emqx/var/lib/emqx + mkdir -p debian/emqx/var/run/emqx + mkdir -p debian/emqx/var/log/emqx + + mkdir -p debian/emqx/usr/lib/emqx/bin + + mkdir -p debian/emqx/etc/emqx + mkdir -p debian/emqx/etc/init.d + + cp bin/* debian/emqx/usr/lib/emqx/bin + + cp -R lib debian/emqx/usr/lib/emqx + cp -R erts* debian/emqx/usr/lib/emqx + cp -R releases debian/emqx/usr/lib/emqx + cp -R etc/* debian/emqx/etc/emqx + cp -R data/* debian/emqx/var/lib/emqx + install -m755 debian/init.script debian/emqx/etc/init.d/emqx + + dh_shlibdeps + +# We have nothing to do by default. +binary-indep: install build-stamp +build-stamp: + +# Build architecture-dependent files here. +binary-arch: install + dh_strip -a + dh_compress -a + dh_installdeb + dh_gencontrol + dh_builddeb + +binary: binary-indep binary-arch diff --git a/deploy/packages/rpm/Makefile b/deploy/packages/rpm/Makefile new file mode 100644 index 000000000..780a4bbd7 --- /dev/null +++ b/deploy/packages/rpm/Makefile @@ -0,0 +1,62 @@ +# Keep this short to avoid bloating beam files with long file path info +TOPDIR := /tmp/emqx +SRCDIR := $(TOPDIR)/$(PKG_VSN) +BUILT := $(SRCDIR)/BUILT +dash := - +none := +space := $(none) $(none) +RPM_VSN ?= $(shell echo $(PKG_VSN) | grep -oE "[0-9]+\.[0-9]+(\.[0-9]+)?") +RPM_REL ?= $(shell echo $(PKG_VSN) | grep -oE "(alpha|beta|rc)\.[0-9]") + +ARCH:=$(shell uname -m) +ifeq ($(ARCH),mips64) +ARCH:=mips64el +endif + +EMQX_NAME=$(subst -pkg,,$(EMQX_BUILD)) + +TAR_PKG := $(EMQX_REL)/_build/$(EMQX_BUILD)/rel/emqx/emqx-$(PKG_VSN).tar.gz +TARGET_PKG := $(EMQX_NAME)-$(SYSTEM)-$(PKG_VSN)-$(ARCH) +ifeq ($(RPM_REL),) + # no tail + RPM_REL := 1 +endif +SOURCE_PKG := emqx-$(SYSTEM)-$(RPM_VSN)-$(RPM_REL).$(ARCH) + +SYSTEMD := $(shell if command -v systemctl >/dev/null 2>&1; then echo yes; fi) +# Not $(PWD) as it does not work for make -C +CURDIR := $(shell pwd) + +SERVICE_SRC := $(if $(SYSTEMD),$(CURDIR)/emqx.service,$(CURDIR)/init.script) +SERVICE_DST := $(if $(SYSTEMD),%{_unitdir}/emqx.service,%{_initddir}/emqx) + +POST_ADDITION := $(if $(SYSTEMD),%systemd_post %{_name}.service,echo post) +PREUN_ADDITION := $(if $(SYSTEMD),%systemd_preun %{_name}.service,echo preun) + +.PHONY: all +all: | $(BUILT) + cd $(SRCDIR) + rpmbuild -v -bb \ + --define "_package_name $(EMQX_NAME)" \ + --define "_name emqx" \ + --define "_topdir $(TOPDIR)" \ + --define "_version $(RPM_VSN)" \ + --define "_reldir $(SRCDIR)" \ + --define "_release $(RPM_REL)" \ + --define "_service_src $(SERVICE_SRC)" \ + --define "_service_dst $(SERVICE_DST)" \ + --define "_post_addition $(POST_ADDITION)" \ + --define "_preun_addition $(PREUN_ADDITION)" \ + --define "_ostype -$(SYSTEM)" \ + --define "_sharedstatedir /var/lib" \ + emqx.spec + mkdir -p $(EMQX_REL)/_packages/$(EMQX_NAME) + cp $(TOPDIR)/RPMS/$(ARCH)/$(SOURCE_PKG).rpm $(EMQX_REL)/_packages/$(EMQX_NAME)/$(TARGET_PKG).rpm + +$(BUILT): + mkdir -p $(TOPDIR) $(SRCDIR) $(SRCDIR)/BUILT + tar zxf $(TAR_PKG) -C $(SRCDIR) + +clean: + rm -rf $(SRCDIR) + diff --git a/deploy/packages/rpm/emqx.service b/deploy/packages/rpm/emqx.service new file mode 100644 index 000000000..2acbc765a --- /dev/null +++ b/deploy/packages/rpm/emqx.service @@ -0,0 +1,15 @@ +[Unit] +Description=emqx daemon +After=network.target + +[Service] +User=emqx +Group=emqx +Type=forking +Environment=HOME=/var/lib/emqx +ExecStart=/bin/sh /usr/bin/emqx start +LimitNOFILE=1048576 +ExecStop=/bin/sh /usr/bin/emqx stop + +[Install] +WantedBy=multi-user.target diff --git a/deploy/packages/rpm/emqx.spec b/deploy/packages/rpm/emqx.spec new file mode 100644 index 000000000..882b7753e --- /dev/null +++ b/deploy/packages/rpm/emqx.spec @@ -0,0 +1,102 @@ +%define debug_package %{nil} +%define _user %{_name} +%define _group %{_name} +%define _conf_dir %{_sysconfdir}/%{_name} +%define _log_dir %{_var}/log/%{_name} +%define _lib_home /usr/lib/%{_name} +%define _var_home %{_sharedstatedir}/%{_name} +%define _build_name_fmt %{_arch}/%{_name}%{?_ostype}-%{_version}-%{_release}.%{_arch}.rpm +%define _build_id_links none + +Name: %{_package_name} +Version: %{_version} +Release: %{_release}%{?dist} +Summary: emqx +Group: System Environment/Daemons +License: Apache License Version 2.0 +URL: https://www.emqx.io +BuildRoot: %{_tmppath}/%{_name}-%{_version}-root +Provides: %{_name} +AutoReq: 0 + +%description +EMQX, a distributed, massively scalable, highly extensible MQTT message broker written in Erlang/OTP. + +%prep + +%build + +%install +mkdir -p %{buildroot}%{_lib_home} +mkdir -p %{buildroot}%{_log_dir} +mkdir -p %{buildroot}%{_unitdir} +mkdir -p %{buildroot}%{_conf_dir} +mkdir -p %{buildroot}%{_bindir} +mkdir -p %{buildroot}%{_var_home} +mkdir -p %{buildroot}%{_initddir} + +cp -R %{_reldir}/lib %{buildroot}%{_lib_home}/ +cp -R %{_reldir}/erts-* %{buildroot}%{_lib_home}/ +cp -R %{_reldir}/releases %{buildroot}%{_lib_home}/ +cp -R %{_reldir}/bin %{buildroot}%{_lib_home}/ +cp -R %{_reldir}/etc/* %{buildroot}%{_conf_dir}/ +cp -R %{_reldir}/data/* %{buildroot}%{_var_home}/ +install -m644 %{_service_src} %{buildroot}%{_service_dst} + +%pre +if [ $1 = 1 ]; then + # Initial installation + /usr/bin/getent group %{_group} >/dev/null || /usr/sbin/groupadd -r %{_group} + if ! /usr/bin/getent passwd %{_user} >/dev/null ; then + /usr/sbin/useradd -r -g %{_group} -m -d %{_sharedstatedir}/%{_name} -c "%{_name}" %{_user} + fi +fi + +%post +if [ $1 = 1 ]; then + ln -s %{_lib_home}/bin/emqx %{_bindir}/emqx + ln -s %{_lib_home}/bin/emqx_ctl %{_bindir}/emqx_ctl +fi +%{_post_addition} +if [ -e %{_initddir}/%{_name} ] ; then + /sbin/chkconfig --add %{_name} +else + systemctl enable %{_name}.service +fi +chown -R %{_user}:%{_group} %{_lib_home} + +%preun +%{_preun_addition} +# Only on uninstall, not upgrades +if [ $1 = 0 ]; then + if [ -e %{_initddir}/%{_name} ] ; then + /sbin/service %{_name} stop > /dev/null 2>&1 + /sbin/chkconfig --del %{_name} + else + systemctl disable %{_name}.service + fi + rm -f %{_bindir}/emqx + rm -f %{_bindir}/emqx_ctl +fi +exit 0 + +%postun +if [ $1 = 0 ]; then + rm -rf %{_lib_home} +fi +exit 0 + +%files +%defattr(-,root,root) +%{_service_dst} +%attr(-,%{_user},%{_group}) %{_lib_home}/* +%attr(-,%{_user},%{_group}) %dir %{_var_home} +%attr(-,%{_user},%{_group}) %config(noreplace) %{_var_home}/* +%attr(-,%{_user},%{_group}) %dir %{_log_dir} +%attr(-,%{_user},%{_group}) %config(noreplace) %{_conf_dir}/* + +%clean +rm -rf %{buildroot} + +%changelog + diff --git a/deploy/packages/rpm/init.script b/deploy/packages/rpm/init.script new file mode 100755 index 000000000..08608f05b --- /dev/null +++ b/deploy/packages/rpm/init.script @@ -0,0 +1,146 @@ +#!/bin/sh +# +# emqx +# +# chkconfig: 2345 80 30 +# description: EMQX, a distributed, massively scalable, highly extensible MQTT message broker written in Erlang/OTP +# processname: beam +# + +# Source function library. +. /etc/rc.d/init.d/functions + +RETVAL=0 +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="EMQX, a distributed, massively scalable, highly extensible MQTT message broker written in Erlang/OTP" +NAME=emqx +DAEMON=/usr/bin/$NAME +lockfile=/var/lock/subsys/$NAME +mkdir -p /var/run/$NAME +pidfile=/var/run/$NAME/$NAME.pid + +# Check for script, config and data dirs +[ -x /usr/bin/$NAME ] || exit 0 +[ -d /etc/$NAME ] || exit 0 +[ -d /var/lib/$NAME ] || exit 0 + +# Read configuration variable file if it is present and readable +[ -r /etc/sysconfig/$NAME ] && . /etc/sysconfig/$NAME + +# `service` strips all environmental VARS so +# if no HOME was set in /etc/sysconfig/$NAME then set one here +# to the data directory for erlexec's sake +if [ -z "$HOME" ]; then + export HOME= +fi + +status -p $pidfile -l $(basename $lockfile) $NAME >/dev/null 2>&1 +running=$? + +find_pid() { + ps ax | grep beam.smp | grep -E "\-progname.+$NAME" | awk '{print $1}' +} + +check_pid_status() { + local pid="$(find_pid)" + if [ "$pid" = "" ]; then + # prog not running? + return 1 + else + # running + return 0 + fi +} + +start() { + # Start daemons. + echo -n $"Starting emqx: " + $DAEMON start + RETVAL=$? + if [ $RETVAL -eq 0 ]; then + touch $lockfile + find_pid > $pidfile + success + else + failure $"$NAME start" + fi + echo + return $RETVAL +} + +stop() { + # Stop daemon. + echo -n $"Shutting down emqx: " + $DAEMON stop 2>/dev/null + for n in $(seq 1 10); do + sleep 1 + check_pid_status + RETVAL=$? + if [ $RETVAL -eq 1 ]; then + break + fi + done + if [ $RETVAL -eq 1 ]; then + rm -f $lockfile $pidfile + success + echo && return 0 + else + failure $"$NAME stop" + echo && return 1 + fi +} + +hardstop() { + echo -n $"Shutting down $NAME: " + su - emqx -c "ps -ef | grep beam.smp | grep '\-progname $NAME ' | grep -v grep | awk '{print \$2}' | xargs kill -9" + for n in $(seq 1 10); do + sleep 1 + check_pid_status + RETVAL=$? + if [ $RETVAL -eq 1 ]; then + break + fi + done + if [ $RETVAL -eq 1 ]; then + rm -f $lockfile $pidfile + success + echo && return 0 + else + failure $"$NAME hardstop" + echo && return 1 + fi +} + +# See how we were called. +case "$1" in + start) + [ $running -eq 0 ] && exit 0 + start + ;; + stop) + stop + ;; + restart|force-reload) + [ $running -eq 0 ] && stop + start + ;; + hardstop) + [ $running -eq 0 ] || exit 0 + hardstop + ;; + condrestart|try-restart) + [ $running -eq 0 ] || exit 0 + restart + ;; + status) + status -p $pidfile -l $(basename $lockfile) $NAME + ;; + ping) + $DAEMON ping || exit $? + ;; + *) + echo $"Usage: $0 {start|stop|restart|force-reload|hardstop|condrestart|try-restart|status|ping}" + exit 1 +esac + +exit $? diff --git a/docker.mk b/docker.mk index 6b7c142da..1b32862ac 100644 --- a/docker.mk +++ b/docker.mk @@ -7,6 +7,7 @@ QEMU_ARCH ?= x86_64 ARCH ?= amd64 QEMU_VERSION ?= v5.0.0-2 OS ?= alpine +PKG_VSN ?= $(shell git describe --tags --match '[0-9]*') EMQX_NAME = $(subst emqx/,,$(TARGET)) ARCH_LIST = amd64 arm64v8 arm32v7 i386 s390x @@ -40,7 +41,6 @@ docker-build: @echo "DOCKER BUILD: qemu arch - $(QEMU_ARCH)." @echo "DOCKER BUILD: docker repo - $(TARGET) " @echo "DOCKER BUILD: emqx name - $(EMQX_NAME)." - @echo "DOCKER BUILD: emqx version - $(EMQX_DEPS_DEFAULT_VSN)." ## Prepare qemu to build images other then x86_64 on travis @echo "PREPARE: Qemu" \ @@ -53,7 +53,7 @@ docker-build: && cd - @docker build --no-cache \ - --build-arg EMQX_DEPS_DEFAULT_VSN=$(EMQX_DEPS_DEFAULT_VSN) \ + --build-arg PKG_VSN=$(PKG_VSN) \ --build-arg BUILD_FROM=emqx/build-env:erl22.3-alpine-$(ARCH) \ --build-arg RUN_FROM=$(ARCH)/alpine:3.11 \ --build-arg EMQX_NAME=$(EMQX_NAME) \ diff --git a/rebar.config.erl b/rebar.config.erl index 592cfa74a..c71721c68 100644 --- a/rebar.config.erl +++ b/rebar.config.erl @@ -266,4 +266,4 @@ str(L) when is_list(L) -> L; str(B) when is_binary(B) -> unicode:characters_to_list(B, utf8). erl_opts_i() -> - [{i, Dir} || Dir <- filelib:wildcard("apps/**/include")]. + [{i, Dir} || Dir <- filelib:wildcard(filename:join(["apps", "**", "include"]))].