diff --git a/CHANGELOG.md b/CHANGELOG.md index c3820c2..35d6b66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 1.2.0 + +- [docker](src/docker) Use the native envvar `SVDIR` instead of `DOCKER_RUNSV_DIR`. +- [docker](src/docker) Update docker-entrypoint.sh. +- [docker](src/docker) Update docker-service.sh. +- [docker](src/docker) Now use docker-config.sh. +- [docker](src/docker) Now use DOCKER_ENTRY_DIR=/etc/docker/entry.d and DOCKER_EXIT_DIR=/etc/docker/exit.d. +- [kopano](src/kopano) 50-kopano-apply-envvars. + # 1.1.8 - [docker](Dockerfile) Configure z-push to use HTTP_X_FORWARDED_FOR. @@ -24,13 +33,13 @@ # 1.1.4 - Use `LDAP_URI` now that the historic directives `LDAP_HOST`, `LDAP_PORT`, `LDAP_PROTOCOL` are no longer supported (8.7.85). -- Split up initialization functions and process supervision. Process supervision stays in entrypoint.sh, whereas the initialization functions are moved to individual files in /etc/entrypoint.d. -- Apache runit script also needs `setup-runit.sh` option; force. +- Split up initialization functions and process supervision. Process supervision stays in docker-entrypoint.sh, whereas the initialization functions are moved to individual files in /etc/docker/entry.d. +- Apache runit script also needs `docker-service.sh` option; force. # 1.1.3 -- The `setup-runit.sh` script now have options: down, force, log, name, source, quiet. -- Fixed the Apache runit script, using the new `setup-runit.sh` script. Stopping the parent process now also stops all child processes. Using the quiet option, Apache does not flood the logs anymore. +- The `docker-service.sh` script now have options: down, force, log, name, source, quiet. +- Fixed the Apache runit script, using the new `docker-service.sh` script. Stopping the parent process now also stops all child processes. Using the quiet option, Apache does not flood the logs anymore. - Added support of the environment variable `LMTP_LISTEN=*:2003`, due to misconfiguration of `kopano-dagent` in recent releases (8.7.84). - Simplified the health check. - Changed repository directory structure to a more general one. diff --git a/Dockerfile b/Dockerfile index 3216dba..3929975 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,26 +9,28 @@ FROM ${ARCH:+$ARCH/}$DIST:$REL AS base LABEL maintainer=mlan ENV DEBIAN_FRONTEND=noninteractive \ PYTHONDONTWRITEBYTECODE=PleaseNoPyCache \ + SVDIR=/etc/service \ DOCKER_BIN_DIR=/usr/local/bin \ - DOCKER_RUNSV_DIR=/etc/service \ - DOCKER_ENTRY_DIR=/etc/entrypoint.d \ - DOCKER_EXIT_DIR=/etc/exitpoint.d \ + DOCKER_ENTRY_DIR=/etc/docker/entry.d \ + DOCKER_EXIT_DIR=/etc/docker/exit.d \ DOCKER_CONF_DIR1=/etc/kopano \ DOCKER_CONF_DIR2=/usr/share/z-push \ - DOCKER_USER=kopano \ + DOCKER_APPL_LIB=/var/lib/kopano \ + KOPANO_SPAMD_LIB=/var/lib/kopano/spamd \ + DOCKER_RUNAS=kopano \ DOCKER_BUILD_DEB_DIR=/tmp/deb \ DOCKER_BUILD_PASSES=1 \ SYSLOG_OPTIONS='-S' \ SYSLOG_LEVEL=5 # -# Copy utility scripts including entrypoint.sh to image +# Copy utility scripts including docker-entrypoint.sh to image # COPY src/*/bin $DOCKER_BIN_DIR/ -COPY src/*/entrypoint.d $DOCKER_ENTRY_DIR/ -COPY src/*/exitpoint.d $DOCKER_EXIT_DIR/ +COPY src/*/entry.d $DOCKER_ENTRY_DIR/ +COPY src/*/exit.d $DOCKER_EXIT_DIR/ # -# Install helpers +# Install helpers. Set bash as default shell. Setup syslogs service. # RUN apt-get update && apt-get install --yes --no-install-recommends \ apt-utils \ @@ -39,7 +41,7 @@ RUN apt-get update && apt-get install --yes --no-install-recommends \ ca-certificates \ tar \ gnupg \ - && setup-runit.sh "syslogd -nO- -l$SYSLOG_LEVEL $SYSLOG_OPTIONS" + && docker-service.sh "syslogd -nO- -l$SYSLOG_LEVEL $SYSLOG_OPTIONS" @@ -51,7 +53,7 @@ FROM base AS base-man # Do not exclude man pages & other documentation # Reinstall all currently installed packages in order to get the man pages back # -ENV DEBIAN_FRONTEND=noninteractive +#ENV DEBIAN_FRONTEND=noninteractive RUN rm -f /etc/dpkg/dpkg.cfg.d/excludes \ && apt-get update \ && dpkg -l | grep ^ii | cut -d' ' -f3 | xargs apt-get install -y --reinstall \ @@ -77,7 +79,7 @@ ARG ARCH=amd64 # variables # ENV DEBIAN_FRONTEND=noninteractive \ - DOCKER_RUNSV_DIR=/etc/service \ + SVDIR=/etc/service \ DOCKER_BIN_DIR=/usr/local/bin \ LMTP_LISTEN=*:2003 \ SA_GROUP=kopano \ @@ -96,9 +98,10 @@ RUN mkdir -p $DOCKER_BUILD_DEB_DIR \ && for i in $(seq ${DOCKER_BUILD_PASSES}); do echo "\033[1;36mKOPANO CORE INSTALL PASS: $i\033[0m" \ && dpkg --install --force-depends --skip-same-version --recursive $DOCKER_BUILD_DEB_DIR \ && apt-get install --yes --no-install-recommends --fix-broken; done \ - && mkdir -p /var/lib/kopano/attachments && chown kopano: /var/lib/kopano/attachments \ + && mkdir -p /var/lib/kopano/attachments && chown $DOCKER_RUNAS: /var/lib/kopano/attachments \ + && mkdir -p $KOPANO_SPAMD_LIB/ham && chown $DOCKER_RUNAS: $KOPANO_SPAMD_LIB/ham \ && rm -rf $DOCKER_BUILD_DEB_DIR \ - && setup-runit.sh \ + && docker-service.sh \ "kopano-dagent -l" \ "kopano-gateway -F" \ "kopano-ical -F" \ @@ -114,15 +117,15 @@ RUN mkdir -p $DOCKER_BUILD_DEB_DIR \ # # Have runit's runsvdir start all services # -CMD runsvdir -P ${DOCKER_RUNSV_DIR} +CMD runsvdir -P ${SVDIR} # # Entrypoint, how container is run # -ENTRYPOINT ["entrypoint.sh"] +ENTRYPOINT ["docker-entrypoint.sh"] # # Check if all services are running # -HEALTHCHECK CMD sv status ${DOCKER_RUNSV_DIR}/* +HEALTHCHECK CMD sv status ${SVDIR}/* @@ -139,7 +142,7 @@ ARG REL # variables # ENV DEBIAN_FRONTEND=noninteractive \ - DOCKER_RUNSV_DIR=/etc/service \ + SVDIR=/etc/service \ DOCKER_BUILD_DEB_DIR=/tmp/deb \ DOCKER_BUILD_PASSES=1 # @@ -156,21 +159,23 @@ RUN apt-get install --yes --no-install-recommends apache2 libapache2-mod-php \ && dpkg --install --force-depends --skip-same-version --recursive $DOCKER_BUILD_DEB_DIR \ && apt-get install --yes --no-install-recommends --fix-broken; done \ && dpkg-reconfigure php7-mapi \ - && conf replace /etc/kopano/webapp/config.php 'define("INSECURE_COOKIES", false);' 'define("INSECURE_COOKIES", true);' \ -# && conf fixmissing /etc/php/7.?/apache2/conf.d/kopano.ini /etc/php/7.?/mods-available/kopano.ini /etc/php5/conf.d/kopano.ini \ - && conf replace /etc/apache2/sites-available/kopano-webapp.conf 'Alias /webapp /usr/share/kopano-webapp' '\\nDocumentRoot /usr/share/kopano-webapp' \ + && . docker-common.sh \ + && . docker-config.sh \ + && dc_replace /etc/kopano/webapp/config.php 'define("INSECURE_COOKIES", false);' 'define("INSECURE_COOKIES", true);' \ +# && dc_fixmissing /etc/php/7.?/apache2/conf.d/kopano.ini /etc/php/7.?/mods-available/kopano.ini /etc/php5/conf.d/kopano.ini \ + && dc_replace /etc/apache2/sites-available/kopano-webapp.conf 'Alias /webapp /usr/share/kopano-webapp' '\\nDocumentRoot /usr/share/kopano-webapp' \ && echo '' >> /etc/apache2/sites-available/kopano-webapp.conf \ - && conf modify /etc/apache2/apache2.conf '^ErrorLog' syslog:user \ + && dc_modify /etc/apache2/apache2.conf '^ErrorLog' syslog:user \ && echo 'CustomLog "||/usr/bin/logger -t apache -i -p user.debug" common' >> /etc/apache2/apache2.conf \ && echo 'ServerName localhost' >> /etc/apache2/apache2.conf \ && mkdir -p /etc/kopano/theme/Custom \ && ln -sf /etc/kopano/theme/Custom /usr/share/kopano-webapp/plugins/. \ -# && conf modify /etc/apache2/apache2.conf '^LogLevel' crit \ +# && dc_modify /etc/apache2/apache2.conf '^LogLevel' crit \ && a2disconf other-vhosts-access-log \ && a2dissite 000-default.conf \ && a2ensite kopano-webapp \ && rm -rf $DOCKER_BUILD_DEB_DIR \ - && setup-runit.sh "-f -s /etc/apache2/envvars -q apache2 -DFOREGROUND -DNO_DETACH -k start" + && docker-service.sh "-f -s /etc/apache2/envvars -q apache2 -DFOREGROUND -DNO_DETACH -k start" # # Ports # @@ -191,7 +196,7 @@ ARG REL # variables # ENV DEBIAN_FRONTEND=noninteractive \ - DOCKER_RUNSV_DIR=/etc/service \ + SVDIR=/etc/service \ DOCKER_BUILD_DEB_DIR=/tmp/deb \ DOCKER_BUILD_PASSES=1 # @@ -208,9 +213,11 @@ RUN debaddr="$(kopano-webaddr.sh --deb final http://repo.z-hub.io/z-push: ${DIST z-push-config-apache \ z-push-autodiscover \ z-push-state-sql \ - && conf addafter /etc/apache2/conf-available/z-push.conf 'Alias /Microsoft-Server-ActiveSync' 'AliasMatch (?i)/Autodiscover/Autodiscover.xml "/usr/share/z-push/autodiscover/autodiscover.php"' '' \ - && conf replace /usr/share/z-push/config.php 'define(\x27USE_CUSTOM_REMOTE_IP_HEADER\x27, false);' 'define(\x27USE_CUSTOM_REMOTE_IP_HEADER\x27, \x27HTTP_X_FORWARDED_FOR\x27);' \ - && conf replace /usr/share/z-push/config.php 'define(\x27LOGBACKEND\x27, \x27filelog\x27);' 'define(\x27LOGBACKEND\x27, \x27syslog\x27);' + && . docker-common.sh \ + && . docker-config.sh \ + && dc_addafter /etc/apache2/conf-available/z-push.conf 'Alias /Microsoft-Server-ActiveSync' 'AliasMatch (?i)/Autodiscover/Autodiscover.xml "/usr/share/z-push/autodiscover/autodiscover.php"' '' \ + && dc_replace /usr/share/z-push/config.php 'define(\x27USE_CUSTOM_REMOTE_IP_HEADER\x27, false);' 'define(\x27USE_CUSTOM_REMOTE_IP_HEADER\x27, \x27HTTP_X_FORWARDED_FOR\x27);' \ + && dc_replace /usr/share/z-push/config.php 'define(\x27LOGBACKEND\x27, \x27filelog\x27);' 'define(\x27LOGBACKEND\x27, \x27syslog\x27);' diff --git a/README.md b/README.md index 046e6b2..837da00 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ To see all available configuration variables you can run `man` within the contai make mail-app-man_server ``` -If you do, you will notice that configuration variable names are all lower case, but they will be matched with all uppercase environment variables by the container `entrypoint.sh` script. +If you do, you will notice that configuration variable names are all lower case, but they will be matched with all uppercase environment variables by the container `docker-entrypoint.sh` script. ## SQL database configuration @@ -279,7 +279,7 @@ You can easily customize the Kopano WebApp see [New! JSON themes in Kopano WebAp ```bash docker cp mytheme/. mail-app:/etc/kopano/theme/Custom docker exec -it mail-app chown -R root: /etc/kopano/theme -docker exec -it mail-app conf replace /etc/kopano/webapp/config.php 'define("THEME", \x27\x27);' 'define("THEME", \x27Custom\x27);' +docker exec -it mail-app run dc_replace /etc/kopano/webapp/config.php 'define("THEME", \x27\x27);' 'define("THEME", \x27Custom\x27);' ``` Please note that it is not possible to rename the directory `/etc/kopano/theme/Custom` within the container without further modifications. diff --git a/demo/Makefile b/demo/Makefile index 21eb5c7..55239c7 100644 --- a/demo/Makefile +++ b/demo/Makefile @@ -57,8 +57,11 @@ $(addsuffix -logs,$(srv_list)): $(addsuffix -sh,$(srv_list)): docker-compose exec $(patsubst %-sh,%,$@) sh +$(addsuffix -env,$(srv_list)): + docker-compose exec $(patsubst %-env,%,$@) env + $(addsuffix -sv,$(srv_list)): - docker-compose exec $(patsubst %-sv,%,$@) sh -c 'sv status $$DOCKER_RUNSV_DIR/*' + docker-compose exec $(patsubst %-sv,%,$@) sh -c 'sv status $$SVDIR/*' $(addsuffix -diff,$(srv_list)): docker container diff $(COMPOSE_PROJECT_NAME)_$(patsubst %-diff,%,$@)_1 @@ -91,7 +94,7 @@ mta-test: | nc -C localhost 25 mta-razor: - docker-compose exec mta conf cntcfg_razor_register + docker-compose exec mta run amavis_register_razor mta-apk_list: docker-compose exec mta /bin/sh -c 'for pkg in $$(apk info 2>/dev/null); do printf "%9s %s\n" $$(apk info -s $$pkg 2>/dev/null | sed -n "2{p;q}") $$pkg; done | sort' @@ -112,6 +115,10 @@ mta-debugtools: mta-htop: mta-debugtools docker-compose exec mta htop +mta-encrypt: + $(eval secret := $(shell whiptail --backtitle "doveadm pw" --title "encrypt password" --inputbox "password" 8 78 secret 3>&1 1>&2 2>&3)) + docker-compose exec mta doveadm pw -p $(secret) + db-test: docker-compose exec db mysqlshow -u $(MYSQL_USER) $(MYSQL_DATABASE) -p$(MYSQL_PASSWORD) diff --git a/src/docker/bin/conf b/src/docker/bin/conf deleted file mode 100755 index 08f906e..0000000 --- a/src/docker/bin/conf +++ /dev/null @@ -1,147 +0,0 @@ -#!/bin/sh - -# -# config -# - -# -# usage -# - -usage() { echo " -USAGE - conf COMMAND FILE [command-options] - -COMMAND - modify - if parameter is found modify its value - uncomment parameter if needed - Examples: - conf modify /etc/clamav/clamd.conf Foreground yes - conf modify /etc/amavisd.conf \$sa_tag_level_deflt = -999; - - replace - match and relpace it with - Examples: - conf replace /etc/amavisd.conf /var/run/clamav/clamd.sock /run/clamav/clamd.sock - - uncommentsection - Remove all leading '#' starting with a line that matches and - ending with an empty line - Examples: - conf uncommentsection /etc/amavisd.conf '# ### http://www.clamav.net/' - - comment - Add leading '#' to line matching - Examples: - conf comment /etc/clamav/freshclam.conf UpdateLogFile - - addafter [] - Add after line matching folowed by an empty line or a line matching - Examples: - conf addafter /etc/amavisd.conf '@local_domains_maps' '$inet_socket_bind = '\''127.0.0.1'\'';' - - fixmissing [] - If is missing create it using the first found - Examples: - conf fixmissing /etc/php/7.?/apache2/conf.d/kopano.ini /etc/php/7.?/mods-available/kopano.ini /etc/php5/conf.d/kopano.ini -FILE - full path to file which will be edited in place -" -} - -# -# commands -# - -_escape() { echo "$@" | sed 's|/|\\\/|g' | sed 's|\$|\\\$|g' | sed "s/""'""/\\\x27/g" ;} -_exglob() { echo $(ls -d $(dirname $1))/$(basename $1) ;} - -modify() { - local cfg_file=$1 - shift - local lhs="$1" - shift - local eq= - local rhs= - if [ "$1" = "=" ]; then - eq="$1" - shift - rhs="$(_escape $@)" - else - rhs="$(_escape $@)" - fi - echo 's/.*('"$lhs"'\s*'"$eq"'\s*)[^ ]+(.*)/\1'"$rhs"'\2/g' $cfg_file - sed -ri 's/.*('"$lhs"'\s*'"$eq"'\s*)[^ ]+(.*)/\1'"$rhs"'\2/g' $cfg_file -} - -replace() { - local cfg_file=$1 - local old="$(_escape $2)" - local new="$(_escape $3)" - echo 's/'"$old"'/'"$new"'/g' $cfg_file - sed -i 's/'"$old"'/'"$new"'/g' $cfg_file -} - -addafter() { - local cfg_file=$1 - local start="$(_escape $2)" - local new="$(_escape $3)" - local stop="$(_escape $4)" - if [ -z "$stop" ]; then - echo '/'"$start"'/!{p;d;}; $!N;s/\n\s*$/\n'"$new"'\n/g' $cfg_file - sed -i '/'"$start"'/!{p;d;}; $!N;s/\n\s*$/\n'"$new"'\n/g' $cfg_file - else - echo '/'"$start"'/!{p;d;}; $!N;s/\n(.*'"$stop"'.*)/\n'"$new"'\n\1/g' $cfg_file - sed -ri '/'"$start"'/!{p;d;}; $!N;s/\n(.*'"$stop"'.*)/\n'"$new"'\n\1/g' $cfg_file - fi - -} - -comment() { - local cfg_file=$1 - local string="$2" - sed -i 's/^'"$string"'/s/^/#/g' $cfg_file -} - -uncommentsection() { - local cfg_file=$1 - local startline="$(_escape $2)" - echo '/^'"$startline"'$/,/^\s*$/s/^#*//g' $cfg_file - sed -i '/^'"$startline"'$/,/^\s*$/s/^#*//g' $cfg_file -} - -fixmissing() { - local cfg_file=$(_exglob $1) - shift - if [ ! -e $cfg_file ]; then - for src_file in $@; do - echo "CHECKING:$src_file" - if [ -e $(_exglob $src_file) ]; then - echo "FOUND:$(_exglob $src_file)" - ln -s $(_exglob $src_file) $cfg_file - break - fi - done - fi -} - -cli_and_exit() { - if [ "$(basename $0)" = conf ]; then - local cmd=$1 - if [ -n "$cmd" ]; then - shift - echo CMD:$cmd ARG:"$@" - $cmd "$@" - else - usage - fi - exit 0 - fi -} - -# -# allow command line interface -# - -cli_and_exit "$@" diff --git a/src/docker/bin/docker-common.sh b/src/docker/bin/docker-common.sh new file mode 100644 index 0000000..401d4de --- /dev/null +++ b/src/docker/bin/docker-common.sh @@ -0,0 +1,102 @@ +#!/bin/sh +# +# docker-common.sh +# +# Defines common functions. Source this file from other scripts. +# +DOCKER_LOGLEVEL=${DOCKER_LOGLEVEL-5} +DOCKER_LOGENTRY=${DOCKER_LOGENTRY-docker-entrypoint.sh} +DOCKER_LOGUSAGE=${DOCKER_LOGUSAGE-usage} + +# +# Write messages to console if interactive or syslog if not. +# Usage: inform priority message +# The priority may be specified numerically or as a facility.level pair. +# Example user.notice, or 1.6 level is one of: +# 0|emerg|1|alert|2|crit|3|err|4|warning|5|notice|6|info|7|debug +# +dc_log() { + local script=$(basename $0) + local stamp="$(dc_log_stamp)" + local prio=$1 + local level=${prio#*.} + local logtag="${script}[${$}]" + local ttytag="$(dc_log_stamp)$(dc_log_tag $level $logtag):" + shift + # Assume interactive if we have stdout open and print usage message if needed. + if [ -t 1 ]; then + echo "$@" + case "$level" in + 0|emerg|1|alert|2|crit|3|err) $DOCKER_LOGUSAGE 2>/dev/null ;; + esac + else + # If we have /dev/log socket send message to logger otherwise to stdout. + if [ -S /dev/log ]; then + logger -t "$logtag" -p "$prio" "$@" + else + if dc_log_level "$level"; then + echo "$ttytag $@" + fi + fi + fi +} + +# +# Color log output. Used if the syslogd daemon is not running. +# +dc_log_tag() { + local level=$1 + local string=$2 + local c l + case $level in + 0|emerg) c=91; l=EMERG ;; + 1|alert) c=91; l=ALERT ;; + 2|crit) c=91; l=CRIT ;; + 3|err) c=91; l=ERROR ;; + 4|warning) c=93; l=WARN ;; + 5|notice) c=92; l=NOTE ;; + 6|info) c=92; l=INFO ;; + 7|debug) c=92; l=DEBUG ;; + esac + printf "\e[%sm%s %s\e[0m\n" $c $string $l +} + +# +# Use $DOCKER_LOGLEVEL during image build phase. Assume we are in build phase if +# $DOCKER_LOGENTRY is not running. +# +dc_log_level() { + local level=$1 + if pidof $DOCKER_LOGENTRY >/dev/null; then + [ "$level" -le "$SYSLOG_LEVEL" ] + else + [ "$level" -le "$DOCKER_LOGLEVEL" ] + fi +} + +# +# Don't add time stamp during image build phase. Assume we are in build phase if +# $DOCKER_LOGENTRY is not running. +# +dc_log_stamp() { + if grep -q $DOCKER_LOGENTRY /proc/1/cmdline; then + date +'%b %e %X ' + fi +} + +# +# Tests +# +dc_is_installed() { apk -e info $1 &>/dev/null ;} # true if pkg is installed + +# +# Update loglevel +# +dc_update_loglevel() { + loglevel=${1-$SYSLOG_LEVEL} + if [ -n "$loglevel" ]; then + dc_log 5 "Setting syslogd level=$loglevel." + docker-service.sh "syslogd -nO- -l$loglevel $SYSLOG_OPTIONS" + [ -n "$DOCKER_RUNFUNC" ] && sv restart syslogd + fi +} diff --git a/src/docker/bin/docker-config.sh b/src/docker/bin/docker-config.sh new file mode 100644 index 0000000..2e3ac62 --- /dev/null +++ b/src/docker/bin/docker-config.sh @@ -0,0 +1,244 @@ +#!/bin/sh +# +# docker-config.sh +# +# Defines common functions. Source this file from other scripts. +# +# Defined in Dockerfile: +# DOCKER_UNLOCK_FILE +# +HOSTNAME=${HOSTNAME-$(hostname)} +DOMAIN=${HOSTNAME#*.} +TLS_KEYBITS=${TLS_KEYBITS-2048} +TLS_CERTDAYS=${TLS_CERTDAYS-30} + +# +# general file manipulation commands, used both during build and run time +# + +_escape() { echo "$@" | sed 's|/|\\\/|g' | sed 's|;|\\\;|g' | sed 's|\$|\\\$|g' | sed "s/""'""/\\\x27/g" ;} + +dc_modify() { + local cfg_file=$1 + shift + local lhs="$1" + shift + local eq= + local rhs= + if [ "$1" = "=" ]; then + eq="$1" + shift + rhs="$(_escape $@)" + else + rhs="$(_escape $@)" + fi + dc_log 7 's/.*('"$lhs"'\s*'"$eq"'\s*)[^#]+(.*)/\1'"$rhs"' \2/g' $cfg_file + sed -ri 's/.*('"$lhs"'\s*'"$eq"'\s*)[^#]+(.*)/\1'"$rhs"' \2/g' $cfg_file +} + +dc_replace() { + local cfg_file=$1 + local old="$(_escape $2)" + local new="$(_escape $3)" + dc_log 7 's/'"$old"'/'"$new"'/g' $cfg_file + sed -i 's/'"$old"'/'"$new"'/g' $cfg_file +} + +dc_addafter() { + local cfg_file=$1 + local startline="$(_escape $2)" + local new="$(_escape $3)" + dc_log 7 '/'"$startline"'/!{p;d;}; $!N;s/\n\s*$/\n'"$new"'\n/g' $cfg_file + sed -i '/'"$startline"'/!{p;d;}; $!N;s/\n\s*$/\n'"$new"'\n/g' $cfg_file +} + +dc_comment() { + local cfg_file=$1 + local string="$2" + dc_log 7 '/^'"$string"'/s/^/#/g' $cfg_file + sed -i '/^'"$string"'/s/^/#/g' $cfg_file +} + +dc_uncommentsection() { + local cfg_file=$1 + local startline="$(_escape $2)" + dc_log 7 '/^'"$startline"'$/,/^\s*$/s/^#*//g' $cfg_file + sed -i '/^'"$startline"'$/,/^\s*$/s/^#*//g' $cfg_file +} + +dc_removeline() { + local cfg_file=$1 + local string="$2" + dc_log 7 '/'"$string"'.*/d' $cfg_file + sed -i '/'"$string"'.*/d' $cfg_file +} + +dc_uniquelines() { + local cfg_file=$1 + dc_log 7 '$!N; /^(.*)\n\1$/!P; D' $cfg_file + sed -ri '$!N; /^(.*)\n\1$/!P; D' $cfg_file +} + + +# +# Persist dirs +# + +# +# Make sure that we have the required directory structure in place under +# DOCKER_PERSIST_DIR. +# +dc_persist_mkdirs() { + local dirs=$@ + for dir in $dirs; do + mkdir -p ${DOCKER_PERSIST_DIR}${dir} + done +} + +# +# Make sure that we have the required directory structure in place under +# DOCKER_PERSIST_DIR. +# +dc_persist_dirs() { + local srcdirs="$@" + local dstdir + if [ -n "$DOCKER_PERSIST_DIR" ]; then + for srcdir in $srcdirs; do + mkdir -p "$srcdir" + dstdir="${DOCKER_PERSIST_DIR}${srcdir}" + mkdir -p "$(dirname $dstdir)" + mv -f "$srcdir" "$(dirname $dstdir)" + ln -sf "$dstdir" "$srcdir" + dc_log 5 "Moving $srcdir to $dstdir" + done + fi +} + +# +# mv dir to persist location and leave a link to it +# +dc_persist_mvdirs() { + local srcdirs="$@" + if [ -n "$DOCKER_PERSIST_DIR" ]; then + for srcdir in $srcdirs; do + if [ -e "$srcdir" ]; then + local dstdir="${DOCKER_PERSIST_DIR}${srcdir}" + local dsthome="$(dirname $dstdir)" + if [ ! -d "$dstdir" ]; then + dc_log 5 "Moving $srcdir to $dstdir" + mkdir -p "$dsthome" + mv "$srcdir" "$dsthome" + ln -sf "$dstdir" "$srcdir" + else + dc_log 4 "$srcdir already moved to $dstdir" + fi + else + dc_log 4 "Cannot find $srcdir" + fi + done + fi +} + +# +# Conditionally change owner of files. +# +dc_chowncond() { + local user=$1 + local dir=$2 + if id $user > /dev/null 2>&1; then + if [ -n "$(find $dir ! -user $user -print -exec chown -h $user: {} \;)" ]; then + dc_log 5 "Changed owner to $user for some files in $dir" + fi + fi +} + +# +# Append entry if it is not already there. If mode is -i then append before last line. +# +dc_cond_append() { + local mode filename lineraw lineesc + case $1 in + -i) mode=i; shift;; + -a) mode=a; shift;; + *) mode=a;; + esac + filename=$1 + shift + lineraw=$@ + lineesc="$(echo $lineraw | sed 's/[\";/*]/\\&/g')" + if [ -e "$filename" ]; then + if [ -z "$(sed -n '/'"$lineesc"'/p' $filename)" ]; then + dc_log 7 "dc_cond_append append: $mode $filename $lineraw" + case $mode in + a) echo "$lineraw" >> $filename;; + i) sed -i "$ i\\$lineesc" $filename;; + esac + else + dc_log 4 "Avoiding duplication: $filename $lineraw" + fi + else + dc_log 7 "dc_cond_append create: $mode $filename $lineraw" + echo "$lineraw" >> $filename + fi +} + +dc_cpfile() { + local suffix=$1 + shift + local cfs=$@ + for cf in $cfs; do + cp "$cf" "$cf.$suffix" + done +} + +dc_mvfile() { + local suffix=$1 + shift + local cfs=$@ + for cf in $cfs; do + mv "$cf" "$cf.$suffix" + done +} + +# +# Prune PID files +# +dc_prune_pidfiles() { + local dirs=$@ + for dir in $dirs; do + if [ -n "$(find -H $dir -type f -name "*.pid" -exec rm {} \; 2>/dev/null)" ]; then + dc_log 5 "Removed orphan pid files in $dir" + fi + done +} + +# +# TLS/SSL Certificates [openssl] +# +dc_tls_setup_selfsigned_cert() { + local cert=$1 + local key=$2 + if ([ ! -s $cert ] || [ ! -s $key ]); then + dc_log 5 "Setup self-signed TLS certificate for host $HOSTNAME" + openssl genrsa -out $key $TLS_KEYBITS + openssl req -x509 -utf8 -new -batch -subj "/CN=$HOSTNAME" \ + -days $TLS_CERTDAYS -key $key -out $cert + fi +} + +# +# Configuration Lock +# +dc_lock_config() { + if dc_is_unlocked; then + rm $DOCKER_UNLOCK_FILE + dc_log 5 "Removing unlock file, locking the configuration." + else + dc_log 5 "No unlock file found, so not touching configuration." + fi +} + +# +# true if there is no lock file or FORCE_CONFIG is not empty +# +dc_is_unlocked() { [ -f "$DOCKER_UNLOCK_FILE" ] || [ -n "$FORCE_CONFIG" ] ;} diff --git a/src/docker/bin/docker-entrypoint.sh b/src/docker/bin/docker-entrypoint.sh new file mode 100755 index 0000000..d70dc8a --- /dev/null +++ b/src/docker/bin/docker-entrypoint.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# set -x +# +# This script need to run as PID 1 allowing it to receive signals from docker +# +# Usage: add the folowing lines in Dockerfile +# ENTRYPOINT ["docker-entrypoint.sh"] +# CMD runsvdir -P ${SVDIR} +# + +# +# Variables +# +DOCKER_ENTRY_DIR=${DOCKER_ENTRY_DIR-/etc/docker/entry.d} +DOCKER_EXIT_DIR=${DOCKER_EXIT_DIR-/etc/docker/exit.d} +SVDIR=${SVDIR-/etc/service} + +# +# Source common functions. +# +. docker-common.sh +. docker-config.sh + +# +# Functions +# + +# +# run_parts dir +# Read and execute commands from files in the _current_ shell environment +# +run_parts() { + for file in $(find $1 -type f -executable 2>/dev/null|sort); do + dc_log 7 run_parts: executing $file + . $file + done +} + +# +# If the service is running, send it the TERM signal, and the CONT signal. +# If both files ./run and ./finish exits, execute ./finish. +# After it stops, do not restart the service. +# +sv_down() { sv down ${SVDIR}/* ;} + +# +# SIGTERM handler +# docker stop first sends SIGTERM, and after a grace period, SIGKILL. +# use exit code 143 = 128 + 15 -- SIGTERM +# +term_trap() { + dc_log 4 "Got SIGTERM, so shutting down." + run_parts "$DOCKER_EXIT_DIR" + sv_down + exit 143 +} + + +# +# Stage 0) Register signal handlers and redirect stderr +# + +exec 2>&1 +trap 'kill $!; term_trap' TERM + +# +# Stage 1) run all entry scripts in $DOCKER_ENTRY_DIR +# + +run_parts "$DOCKER_ENTRY_DIR" + +# +# Stage 2) run provided arguments in the background +# Start services with: runsvdir -P ${SVDIR} +# + +"$@" & + +# +# Stage 3) wait forever so we can catch the SIGTERM +# +while true; do + tail -f /dev/null & wait $! +done diff --git a/src/docker/bin/docker-runfunc.sh b/src/docker/bin/docker-runfunc.sh new file mode 100755 index 0000000..ae37abc --- /dev/null +++ b/src/docker/bin/docker-runfunc.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# +# docker-runfunc.sh +# +# Allow functions to be accessed from the commandline. +# + +# +# Source common functions. +# +. docker-common.sh + +# +# dr_docker_call_func "$@" +# +dr_docker_call_func() { + export DOCKER_RUNFUNC="$@" + local cmd=$1 + shift + dc_log 7 "CMD:$cmd ARG:$@" + $cmd "$@" + exit 0 +} + +# +# dr_docker_run_parts dir name +# Read and execute commands from files in the _current_ shell environment. +# +dr_docker_run_parts() { + for file in $(find $1 -type f -name "$2" -executable 2>/dev/null|sort); do + dc_log 7 run_parts: executing $file + . $file + done +} + +# +# Source files with function definitions. +# +dr_docker_run_parts "$DOCKER_ENTRY_DIR" "1*" + +# +# Call function. +# +dr_docker_call_func "$@" diff --git a/src/docker/bin/setup-runit.sh b/src/docker/bin/docker-service.sh similarity index 76% rename from src/docker/bin/setup-runit.sh rename to src/docker/bin/docker-service.sh index 871737f..ca94b66 100755 --- a/src/docker/bin/setup-runit.sh +++ b/src/docker/bin/docker-service.sh @@ -1,10 +1,11 @@ #!/bin/sh # -# setup-runit.sh +# docker-service.sh # +. docker-common.sh -# use /etc/service if $DOCKER_RUNSV_DIR not already defined -DOCKER_RUNSV_DIR=${DOCKER_RUNSV_DIR-/etc/service} +# use /etc/service if $SVDIR not already defined +SVDIR=${SVDIR-/etc/service} DOCKER_SVLOG_DIR=${DOCKER_SVLOG_DIR-/var/log/sv} DOCKER_RUN_DIR=${DOCKER_RUN_DIR-/var/run} @@ -14,10 +15,10 @@ DOCKER_RUN_DIR=${DOCKER_RUN_DIR-/var/run} usage() { cat <<-!cat NAME - setup-runit.sh + docker-service.sh SYNOPSIS - setup-runit.sh [-d] [-f] [-h] [-l] [-n name] [-s file] [-q] command [args] + docker-service.sh [-d] [-f] [-h] [-l] [-n name] [-s file] [-q] command [args] OPTIONS -d default down @@ -29,7 +30,7 @@ usage() { -q send stdout and stderr to /dev/null EXAMPLES - setup-runit.sh "kopano-dagent -l" "-d kopano-grapi serve" + docker-service.sh "kopano-dagent -l" "-d kopano-grapi serve" "-q -s /etc/apache2/envvars apache2 -DFOREGROUND -DNO_DETACH -k start" !cat @@ -43,17 +44,6 @@ pid_name() { echo "${DOCKER_RUN_DIR}/${dir_name}/${pid_name}.pid" } -inform() { - name=$(basename $0) - case "$1" in - 0) pre_string="\e[1m\e[92mINFO ($name)\e[0m";; - 1) pre_string="\e[1m\e[93mWARN ($name)\e[0m";; - 2) pre_string="\e[1m\e[91mERROR ($name)\e[0m";; - esac - shift - printf "$pre_string %s\n" "$*" -} - add_opt() { if [ -z "$options" ]; then options=$1 @@ -85,14 +75,14 @@ init_service() { shift $((OPTIND -1)) cmd=$(which "$1") sv_name=${sv_name-$(base_name $1)} - runsv_dir=$DOCKER_RUNSV_DIR/$sv_name + runsv_dir=$SVDIR/$sv_name svlog_dir=$DOCKER_SVLOG_DIR/$sv_name if [ -n "$sv_force" ]; then forcepid="$(echo rm -f $(pid_name $sv_name)*)" fi shift if [ ! -z "$cmd" ]; then - inform 0 "Setting up ($sv_name) options ($options) args ($@)" + dc_log 5 "Setting up ($sv_name) options ($options) args ($@)" mkdir -p $runsv_dir cat <<-!cat > $runsv_dir/run #!/bin/sh -e @@ -117,7 +107,6 @@ init_service() { fi } - # # run # diff --git a/src/docker/bin/entrypoint.sh b/src/docker/bin/entrypoint.sh deleted file mode 100755 index ba7946d..0000000 --- a/src/docker/bin/entrypoint.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env bash -# set -x -# -# This script need to run as PID 1 allowing it to receive signals from docker -# -# Usage: add the folowing lines in Dockerfile -# ENTRYPOINT ["entrypoint.sh"] -# CMD runsvdir -P ${DOCKER_RUNSV_DIR} -# - -# -# Variables -# - -DOCKER_ENTRY_DIR=${DOCKER_ENTRY_DIR-/etc/entrypoint.d} -DOCKER_EXIT_DIR=${DOCKER_EXIT_DIR-/etc/exitpoint.d} -DOCKER_RUNSV_DIR=${DOCKER_RUNSV_DIR-/etc/service} - -# -# Functions -# - -# -# Run all executable scipts in entry direcory -# -run_dir() { - local rundir=${1} - if [ -d "$rundir" ]; then - run-parts "$rundir" - fi -} - -# -# If the service is running, send it the TERM signal, and the CONT signal. -# If ./run exits, start ./finish if it exists. -# After it stops, do not restart service. -# -sv_down() { sv down ${DOCKER_RUNSV_DIR}/* ;} - -# -# SIGTERM handler -# docker stop first sends SIGTERM, and after a grace period, SIGKILL. -# use exit code 143 = 128 + 15 -- SIGTERM -# -term_trap() { - run_dir "$DOCKER_EXIT_DIR" - sv_down - exit 143 -} - - -# -# Stage 0) Register signal hanglers and redirect stderr -# - -exec 2>&1 -trap 'kill ${!}; term_trap' SIGTERM - -# -# Stage 1) run all entry scripts in $DOCKER_ENTRY_DIR -# - -run_dir "$DOCKER_ENTRY_DIR" - -# -# Stage 2) run provided arguments in the background -# Start services with: runsvdir -P ${DOCKER_RUNSV_DIR} -# - -"$@" & - -# -# Stage 3) wait forever so we can catch the SIGTERM -# -while true; do - tail -f /dev/null & wait ${!} -done diff --git a/src/docker/bin/run b/src/docker/bin/run new file mode 120000 index 0000000..8839d0b --- /dev/null +++ b/src/docker/bin/run @@ -0,0 +1 @@ +docker-runfunc.sh \ No newline at end of file diff --git a/src/docker/entry.d/50-docker-update-loglevel b/src/docker/entry.d/50-docker-update-loglevel new file mode 100755 index 0000000..602f2e2 --- /dev/null +++ b/src/docker/entry.d/50-docker-update-loglevel @@ -0,0 +1,7 @@ +#!/bin/sh +# +# 50-docker-update-loglevel +# +# If SYSLOG_LEVEL is not empty update syslog level +# +dc_update_loglevel diff --git a/src/docker/entrypoint.d/50_update-loglevel b/src/docker/entrypoint.d/50_update-loglevel deleted file mode 100755 index af06aa7..0000000 --- a/src/docker/entrypoint.d/50_update-loglevel +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -# -# 50_update-loglevel -# -# If SYSLOG_LEVEL is not empty update syslog level -# - -inform() { printf "entrypoint[$$]: INFO:$(basename $0): $*.\n" ;} - -# -# run -# - -if [ -n "$SYSLOG_LEVEL" ]; then - inform "Setting syslogd level=$SYSLOG_LEVEL" - setup-runit.sh "syslogd -nO- -l$SYSLOG_LEVEL $SYSLOG_OPTIONS" -fi diff --git a/src/docker/exitpoint.d/.placeholder b/src/docker/exit.d/.placeholder similarity index 100% rename from src/docker/exitpoint.d/.placeholder rename to src/docker/exit.d/.placeholder diff --git a/src/kopano/entrypoint.d/50_update-config b/src/kopano/entry.d/50-kopano-apply-envvars similarity index 73% rename from src/kopano/entrypoint.d/50_update-config rename to src/kopano/entry.d/50-kopano-apply-envvars index c5e67f8..ffd9241 100755 --- a/src/kopano/entrypoint.d/50_update-config +++ b/src/kopano/entry.d/50-kopano-apply-envvars @@ -1,6 +1,6 @@ #!/bin/bash # -# 50_update_config +# 50-kopano-apply-envvars # # Kopano now installs without any cfg files, so we just write custom values # into their target cfg file. @@ -39,9 +39,7 @@ sqlstate_env_vars="STATE_SQL_ENGINE STATE_SQL_SERVER STATE_SQL_PORT STATE_SQL_DA # Define helpers # -inform() { printf "entrypoint[$$]: INFO:$(basename $0): $*.\n" ;} - -_kopano_cfg_gen() { +_kopano_apply_envvars_gen() { # do not touch existing cfg files local cfg_file=$1 shift @@ -49,43 +47,43 @@ _kopano_cfg_gen() { if [ ! -e $cfg_file ]; then for env_var in $env_vars; do if [ -n "${!env_var}" ]; then - inform "Setting ${env_var,,} = ${!env_var} in $cfg_file" + dc_log 5 "Setting ${env_var,,} = ${!env_var} in $cfg_file" echo ${env_var,,} = ${!env_var} >> $cfg_file fi done fi } -_php_cfg_gen() { +_kopano_apply_phpenvvars_gen() { local cfg_file=$1 shift local env_vars=$@ if [ -e $cfg_file ]; then for env_var in $env_vars; do if [ -n "${!env_var}" ]; then - inform "Setting ${env_var} = ${!env_var} in $cfg_file" + dc_log 5 "Setting ${env_var} = ${!env_var} in $cfg_file" sed -ri "s/(\s*define).+${env_var}.+/\1\(\x27${env_var}\x27, \x27${!env_var}\x27\);/g" $cfg_file fi done fi } -kopano_cfg() { - _kopano_cfg_gen $server_cfg_file $server_env_vars - _kopano_cfg_gen $ldap_cfg_file $ldap_env_vars - _kopano_cfg_gen $spooler_cfg_file $spooler_env_vars - _kopano_cfg_gen $dagent_cfg_file $dagent_env_vars - _kopano_cfg_gen $spamd_cfg_file $spamd_env_vars +kopano_apply_envvars() { + _kopano_apply_envvars_gen $server_cfg_file $server_env_vars + _kopano_apply_envvars_gen $ldap_cfg_file $ldap_env_vars + _kopano_apply_envvars_gen $spooler_cfg_file $spooler_env_vars + _kopano_apply_envvars_gen $dagent_cfg_file $dagent_env_vars + _kopano_apply_envvars_gen $spamd_cfg_file $spamd_env_vars } -php_cfg() { - _php_cfg_gen $zpush_cfg_file $zpush_env_vars - _php_cfg_gen $sqlstate_cfg_file $sqlstate_env_vars +kopano_apply_phpenvvars() { + _kopano_apply_phpenvvars_gen $zpush_cfg_file $zpush_env_vars + _kopano_apply_phpenvvars_gen $sqlstate_cfg_file $sqlstate_env_vars } # # run # -kopano_cfg -php_cfg +kopano_apply_envvars +kopano_apply_phpenvvars diff --git a/src/kopano/entrypoint.d/10_fix_spamd_ham b/src/kopano/entrypoint.d/10_fix_spamd_ham deleted file mode 100755 index 72434e9..0000000 --- a/src/kopano/entrypoint.d/10_fix_spamd_ham +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh -# -# 10_fix_spamd_ham -# -# kopano-spamd creates /var/lib/kopano/spamd/ham with root ownership, -# but needs to be kopano -# - -# -# config -# - -DOCKER_USER=${DOCKER_USER-kopano} -DOCKER_SPAMD_DIR=${DOCKER_SPAMD_DIR-/var/lib/kopano/spamd} - -# -# define helpers -# - -inform() { printf "entrypoint[$$]: INFO:$(basename $0): $*.\n" ;} - -fixattr() { - for dir in $@; do - if [ -n "$(find $dir ! -user $DOCKER_USER -print -exec chown -h $DOCKER_USER: {} \;)" ]; then - inform "Changed owner to $DOCKER_USER for some files in $dir" - fi - if [ -n "$(find -L $dir ! -user $DOCKER_USER -print -exec chown $DOCKER_USER: {} \;)" ]; then - inform "Changed owner to $DOCKER_USER for some files in $dir" - fi - if [ -n "$(find -H $dir ! -perm -u+rw -print -exec chmod u+rw {} \;)" ]; then - inform "Changed permission to rw for some files in $dir" - fi - done -} - -# -# run -# - -mkdir -p $DOCKER_SPAMD_DIR/ham -fixattr $DOCKER_SPAMD_DIR/ham diff --git a/src/kopano/entrypoint.d/20_fix_attr b/src/kopano/entrypoint.d/20_fix_attr deleted file mode 100755 index f4d530b..0000000 --- a/src/kopano/entrypoint.d/20_fix_attr +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/sh -# -# 20_fix_attr -# -# Make sure all files in named directories are RW by user $DOCKER_USER. -# - -# -# config -# - -DOCKER_USER=${DOCKER_USER-kopano} -DOCKER_ATCH_DIR=${DOCKER_ATCH_DIR-/var/lib/kopano/attachments} -DOCKER_SPAMD_DIR=${DOCKER_SPAMD_DIR-/var/lib/kopano/spamd} - -# -# define helpers -# - -inform() { printf "entrypoint[$$]: INFO:$(basename $0): $*.\n" ;} - -fixattr() { - for dir in $@; do - if [ -n "$(find $dir ! -user $DOCKER_USER -print -exec chown -h $DOCKER_USER: {} \;)" ]; then - inform "Changed owner to $DOCKER_USER for some files in $dir" - fi - if [ -n "$(find -L $dir ! -user $DOCKER_USER -print -exec chown $DOCKER_USER: {} \;)" ]; then - inform "Changed owner to $DOCKER_USER for some files in $dir" - fi - if [ -n "$(find -H $dir ! -perm -u+rw -print -exec chmod u+rw {} \;)" ]; then - inform "Changed permission to rw for some files in $dir" - fi - done -} - -# -# run -# - -fixattr $DOCKER_ATCH_DIR $DOCKER_SPAM_DIR