commit 41d46013003ecaa7a121824cb84954901385b798 Author: mlan Date: Fri Feb 8 10:57:43 2019 +0100 First commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cdb1a82 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.git +.gitignore +.github +.gitattributes +READMETEMPLATE.md +README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ffca4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +deb +tmp +*.swp +local_* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..22776ed --- /dev/null +++ b/Dockerfile @@ -0,0 +1,221 @@ +ARG DIST=ubuntu +ARG REL=18.04 +ARG ARCH=amd64 + + + +FROM $ARCH/$DIST:$REL AS base +LABEL maintainer=mlan +ENV DEBIAN_FRONTEND=noninteractive \ + docker_build_runit_root=/etc/service \ + docker_build_deb_dir=/tmp/deb \ + docker_build_passes=1 \ + SYSLOG_LEVEL=4 +# +# Copy helpers +# +COPY assets/setup-runit.sh /usr/local/bin/ +# +# Install helpers +# +RUN apt-get update && apt-get install --yes --no-install-recommends \ + apt-utils \ + busybox-syslogd \ + runit \ + wget \ + curl \ + ca-certificates \ + tar \ + gnupg \ + && setup-runit.sh "syslogd -n -O /dev/stdout -l $SYSLOG_LEVEL" + + + +FROM base AS base-man +# +# get man pages to work +# +# +# Do not exclude man pages & other documentation +# Reinstall all currently installed packages in order to get the man pages back +# +ENV DEBIAN_FRONTEND=noninteractive +RUN rm /etc/dpkg/dpkg.cfg.d/excludes \ + && apt-get update \ + && dpkg -l | grep ^ii | cut -d' ' -f3 | xargs apt-get install -y --reinstall \ + && apt-get install --yes --no-install-recommends \ + man \ + manpages \ + bash-completion \ + && rm -r /var/lib/apt/lists/* + + + +# +# Kopano-core +# +FROM base-man AS kopano-core +# +# build arguments +# +ARG DIST +ARG REL +ARG ARCH +# +# variables +# +ENV DEBIAN_FRONTEND=noninteractive \ + docker_build_runit_root=/etc/service \ + docker_build_deb_dir=/tmp/deb \ + docker_build_passes=1 +# +# Copy helpers +# +COPY assets/kopano-webaddr.sh /usr/local/bin/ +COPY assets/dpkg-version.sh /usr/local/bin/ +COPY assets/conf /usr/local/bin/ +COPY assets/entrypoint.sh /usr/local/bin/ +COPY assets/healthcheck.sh /usr/local/bin/ +# +# Install kopano-core +# +RUN mkdir -p $docker_build_deb_dir \ + && webaddr=$(kopano-webaddr.sh core \ + https://download.kopano.io/community ${DIST} ${REL} ${ARCH}) \ + && echo "$webaddr<->${DIST} ${REL} ${ARCH}<-" \ + && curl $webaddr | tar -xzC $docker_build_deb_dir \ + && apt-get update \ + && 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 \ +# && rm -rf $docker_build_deb_dir \ + && setup-runit.sh \ + "kopano-dagent -l" \ + "kopano-gateway -F" \ + "kopano-ical -F" \ + "kopano-search -F" \ + "kopano-server -F" \ + "kopano-spooler -F" \ + && setup-runit.sh --down \ + "kopano-grapi serve" \ + "kopano-kapid serve --log-timestamp=false" \ + "kopano-konnectd serve --log-timestamp=false" \ + "kopano-monitor -F" \ + "kopano-presence -F" \ + "kopano-spamd -F" +# +# Entrypoint, how container is run +# +HEALTHCHECK --interval=5m --timeout=3s CMD healthcheck.sh +ENTRYPOINT ["entrypoint.sh"] + + + +# +# Kopano-webapp +# +FROM kopano-core AS kopano-core-webapp +# +# build arguments +# +ARG DIST +ARG REL +ARG ARCH +# +# variables +# +ENV DEBIAN_FRONTEND=noninteractive \ + docker_build_runit_root=/etc/service \ + docker_build_deb_dir=/tmp/deb \ + docker_build_passes=1 +# +# Install Kopano webapp +# +RUN apt-get install --yes --no-install-recommends apache2 libapache2-mod-php7.2 \ + && mkdir -p $docker_build_deb_dir \ + && webaddr=$(kopano-webaddr.sh webapp \ + https://download.kopano.io/community ${DIST} ${REL} all) \ + && echo "$webaddr<->${DIST} ${REL} all<-" \ + && curl $webaddr | tar -xzC $docker_build_deb_dir \ + && apt-get update \ + && for i in $(seq ${docker_build_passes}); do echo "\033[1;36mKOPANO WEBAPP 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 \ + && 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' \ + && echo '' >> /etc/apache2/sites-available/kopano-webapp.conf \ + && conf modify /etc/apache2/apache2.conf '^ErrorLog' syslog:user \ + && echo 'CustomLog "||/usr/bin/logger -t apache -i -p user.notice" vhost_combined' >> /etc/apache2/apache2.conf \ + && echo 'CustomLog "||/usr/bin/logger -t apache -i -p user.info" combined' >> /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 \ +# && a2disconf other-vhosts-access-log \ + && a2dissite 000-default.conf \ + && a2ensite kopano-webapp \ +# && rm -rf $docker_build_deb_dir \ + && setup-runit.sh "apache2ctl -D FOREGROUND -k start" +# +# Ports +# +EXPOSE 80 443 + + + +# +# Z-Push +# +FROM kopano-core-webapp AS kopano-full +# +# build arguments +# +ARG DIST +ARG REL +ARG ARCH +# +# variables +# +ENV DEBIAN_FRONTEND=noninteractive \ + docker_build_runit_root=/etc/service \ + docker_build_deb_dir=/tmp/deb \ + docker_build_passes=1 +# +# Add Z-Push repository and install Z-Push configured to be used with kopano and apache +# +RUN debaddr="$(kopano-webaddr.sh --deb final http://repo.z-hub.io/z-push: ${DIST} ${REL})" \ + && echo "deb $debaddr/ /" > /etc/apt/sources.list.d/z-push.list \ + && wget -qO - $debaddr/Release.key | apt-key add - \ + && mkdir -p /var/lib/z-push && chown www-data:www-data /var/lib/z-push \ + && mkdir -p /var/log/z-push && chown www-data:www-data /var/log/z-push \ + && apt-get update && apt-get install --yes --no-install-recommends \ + z-push-backend-kopano \ + z-push-kopano \ + 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(\x27LOGBACKEND\x27, \x27filelog\x27);' 'define(\x27LOGBACKEND\x27, \x27syslog\x27);' + + + +FROM kopano-full AS kopano-debugtools +# +# Optionaly install debug tools +# +RUN apt-get update && apt-get install --yes --no-install-recommends \ + less \ + nano \ + ldap-utils \ + htop \ + net-tools \ + lsof \ + iputils-ping +# +# clean up +# +#RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + + + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..a31e1a4 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 mlan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4671746 --- /dev/null +++ b/Makefile @@ -0,0 +1,71 @@ +-include *.mk + +BLD_ARG ?= --build-arg DIST=ubuntu --build-arg REL=18.04 --build-arg ARCH=amd64 +IMG_REPO ?= mlan/kopano +IMG_VER ?= $(shell assets/kopano-webaddr.sh -VV) + +CNT_NAME ?= kopano-default +CNT_PORT ?= -p 80:80 +CNT_ENV ?= +CNT_VOL ?= + +.PHONY: build build-all bulid-core build-full build-debugtools \ + variables push shell exec run run-fg start stop rm-container rm-image release logs + +build: Dockerfile + docker build $(BLD_ARG) --target kopano-full -t $(IMG_REPO)\:$(IMG_VER) . + +build-all: build-core build-full build-debugtools + +build-core: Dockerfile + docker build $(BLD_ARG) --target kopano-core \ + -t $(IMG_REPO)\:$(IMG_VER)-core \ + -t $(IMG_REPO)\:latest-core . + +build-full: Dockerfile + docker build $(BLD_ARG) --target kopano-full \ + -t $(IMG_REPO)\:$(IMG_VER) \ + -t $(IMG_REPO)\:$(IMG_VER)-full \ + -t $(IMG_REPO)\:latest \ + -t $(IMG_REPO)\:latest-full . + +build-debugtools: Dockerfile + docker build $(BLD_ARG) --target kopano-debugtools \ + -t $(IMG_REPO)\:$(IMG_VER)-debugtools \ + -t $(IMG_REPO)\:latest-debugtools . + +variables: + make -pn | grep -A1 "^# makefile"| grep -v "^#\|^--" | sort | uniq + + +push: + docker push $(IMG_REPO)\:$(IMG_VER) + +shell: + docker run --rm --name $(CNT_NAME)-$(CNT_INST) -i -t $(CNT_PORT) $(CNT_VOL) $(CNT_ENV) $(IMG_REPO)\:$(IMG_VER) /bin/bash + +exec: + docker exec -it $(CNT_NAME) /bin/bash + +run-fg: + docker run --rm --name $(CNT_NAME) $(CNT_PORT) $(CNT_VOL) $(CNT_ENV) $(IMG_REPO)\:$(IMG_VER) + +run: + docker run --rm -d --name $(CNT_NAME) $(CNT_PORT) $(CNT_VOL) $(CNT_ENV) $(IMG_REPO)\:$(IMG_VER) + +logs: + docker container logs $(CNT_NAME) + +stop: + docker stop $(CNT_NAME) + +rm-container: + docker rm $(CNT_NAME) + +rm-image: + docker image rm $(IMG_REPO):$(IMG_VER) + +release: build + make push -e IMG_VER=$(IMG_VER) + +default: build diff --git a/README.md b/README.md new file mode 100644 index 0000000..2f57a89 --- /dev/null +++ b/README.md @@ -0,0 +1,269 @@ +# The mlan/kopano repository + +This (unofficial) repository provides dockerized web mail service as well as ActiveSync, ICAL, IMAP and POP3 service. It is based on [Kopano]() core components, as well as the Kopano WebApp and [Z-Push](http://z-push.org/). The image uses [nightly built packages](https://download.kopano.io/community/) which are provided by the Kopano community. + +Hopefully this repository can be retired once the Kopano community make official images available. There is some evidence of such activity on [dockerhub:kopano](https://hub.docker.com/u/kopano). + +## Feature overview + +Brief feature list follows below + +- Groupware server [Kopano WebApp](https://kopano.io/) +- ActiveSync server [Z-Push](http://z-push.org/) +- Multi-staged build providing the images `-full `, `-debugtools` , `-core` and `-webapp` +- Configuration using environment variables +- Log directed to docker daemon with configurable level +- Built in utility script `conf` helping configuring Kopano components, WebApp and Z-Push +- Health check +- Hook for theming + +## Tags overview + +The mlan/kopano repository contains a multi staged built. You select which build using the appropriate tag. + +The version part of the tag is `latest` or the combined revision numbers of the nightly kopano-core and kopano-webapp package suits that was available when building this image. For example, `8.7.80-3.5.2` indicates that the image was built using the 8.7.80 version of Kopano core and 3.5.2 version of Kopano webapp. + +The build part of the tag is one of `-full `, `-debugtools` , `-core` and soon also `-webapp`. The image with tag ending in `-full` or without ending contain Kopano core components, as well as, the Kopano webapp and z-push. The image with tag ending in `-debugtools` also contains some debug tools. The image with tag ending in `-core` contains the kopano-core components proving the server and imap, pop3 and ical access. The image with tag ending in `-webapp` contains the Kopano webapp and z-push proving web and active sync service which will depend on a kopano server running in a separate container or elsewhere. + +To exemplify the usage of the tags, lets assume that the latest version tag is `8.7.80-3.5.2`. In this case `latest`, `8.7.80-3.5.2`, `latest-full` and `8.7.80-3.5.2-full` all identify the same image. + +# Usage + +In most use cases the `mlan/kopano` container also needs a SQL database (e.g., [MySQL](https://hub.docker.com/_/mysql) or [MariaDB](https://hub.docker.com/_/mariadb)), Mail Transfer Agent (e.g., [Postfix](http://www.postfix.org/)) and authentication (e.g., [OpenLDAP](https://www.openldap.org/)). Docker images of such services are available. The docker compose example below is used to demonstrate how to configure these services. + +```bash +docker run -d --name mail-app -p 80:80 mlan/kopano +``` + +## Docker compose example + +An example of how to configure an web mail server using docker compose is given below. It defines four services, `mail-app`, `mail-mta`, `mail-db` and `auth`, which are the web mail server, the mail transfer agent, the SQL database and authentication respectively. + +```yaml +version: '3.7' + +services: + mail-app: + image: mlan/kopano:8.7.80-3.5.2 + restart: unless-stopped + networks: + - backend + ports: + - "80:80" + labels: + - traefik.enable=true + - traefik.frontend.rule=Host:mail.${DOMAIN-docker.localhost} + - traefik.docker.network=${COMPOSE_PROJECT_NAME}_proxy + - traefik.port=80 + depends_on: + - auth + - mail-db + - mail-mta + environment: + - USER_PLUGIN=ldap + - LDAP_HOST=auth + - MYSQL_HOST=mail-db + - SMTP_SERVER=mail-mta + - LDAP_SEARCH_BASE=${LDAP_BASE} + - LDAP_USER_TYPE_ATTRIBUTE_VALUE=kopano-user + - LDAP_GROUP_TYPE_ATTRIBUTE_VALUE=kopano-group + - LDAP_USER_SEARCH_FILTER=(kopanoAccount=1) + - SYSLOG_LEVEL=4 + env_file: + - .init.env + volumes: + - mail-conf:/etc/kopano + - mail-atch:/var/lib/kopano/attachments + - mail-sync:/var/lib/z-push + + mail-mta: + image: mlan/postfix-amavis:3.8 + restart: unless-stopped + hostname: ${MAIL_SRV-mx}.${MAIL_DOMAIN-docker.localhost} + networks: + - backend + ports: + - "25:25" + depends_on: + - auth + environment: + - LDAP_HOST=auth + - DAGENT_TRANSPORT=lmtp:mail-app:2003 + - SMTP_RELAY_HOSTAUTH=${SMTP_RELAY_HOSTAUTH} + - SMTP_TLS_SECURITY_LEVEL=${SMTP_TLS_SECURITY_LEVEL-} + - SMTP_TLS_WRAPPERMODE=${SMTP_TLS_WRAPPERMODE-no} + - LDAP_USER_BASE=${LDAP_USEROU},${LDAP_BASE} + - LDAP_GROUP_BASE=${LDAP_GROUPOU},${LDAP_BASE} + - LDAP_QUERY_FILTER_USER=(&(kopanoAccount=1)(mail=%s)) + - LDAP_QUERY_FILTER_ALIAS=(&(kopanoAccount=1)(kopanoAliases=%s)) + - LDAP_QUERY_FILTER_GROUP=(&(objectclass=kopano-group)(mail=%s)) + - LDAP_QUERY_FILTER_EXPAND=(&(objectclass=kopano-user)(uid=%s)) + - DKIM_SELECTOR=${DKIM_SELECTOR-default} + - SYSLOG_LEVEL=5 + env_file: + - .init.env + volumes: + - mail-mta:/var + - proxy-acme:/acme + + mail-db: + image: mariadb + restart: unless-stopped + command: ['--log_warnings=1'] + networks: + - backend + environment: + - LANG=C.UTF-8 + env_file: + - .init.env + volumes: + - mail-db:/var/lib/mysql + + auth: + image: mlan/openldap:1.0 + restart: unless-stopped + networks: + - backend + environment: + - LDAP_LOGLEVEL=parse + volumes: + - auth-conf:/srv/conf + - auth-data:/srv/data + +networks: + backend: + +volumes: + mail-conf: + mail-atch: + mail-db: + mail-mta: + mail-sync: + +``` + +## Environment variables + +When you create the `mlan/kopano` container, you can adjust the configuration of the Kopano server by passing one or more environment variables or on the docker run command line. Note that any pre-existing configuration files within the container will be left untouched. + +To see all available configuration variables you can run `man` within the container, for example like this: + +```bash +docker exec mail-app man kopano-server.cfg +``` + +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 script. + +## SQL database configuration + +The Kopano server uses a SQL database, which needs to be initiated, see below. Once the SQL database has been initiated you can create the Kopano container and configure it to use the SQL database using environment variables. + +#### `MYSQL_HOST` + +The hostname of the MySQL server to use. Default `MYSQL_HOST=localhost`. + +#### `MYSQL_PORT` + +The port of the MySQL server to use. Default `MYSQL_PORT=3306` + +#### `MYSQL_USER` + +The user under which we connect with MySQL. Default `MYSQL_USER=root`. For security reasons it is probably wise not to use the `root` user. Use the same name as was used when initiating the SQL database, see below. + +#### `MYSQL_PASSWORD` + +The password to use for MySQL. It is possible to leave it empty for no password, but that is advised against. Default `MYSQL_PASSWORD=`. Use the same password as was used when initiating the SQL database, see below. + +#### `MYSQL_DATABASE` + +The MySQL database to connect to. Default `MYSQL_DATABASE=kopano`. Use the same database name as was used when initiating the SQL database, see below. + +#### `ATTACHMENT_STORAGE` + +The location where attachments are stored. This can be in the MySQL database, or as separate files. The drawback of `database` is that the large data of attachment will push useful data from the MySQL cache. The drawback of separate files is that a `mysqldump` is not enough for a full disaster recovery. Possible values: `database`, `files`, `files_v2` (experimental). Default: `ATTACHMENT_STORAGE=files` + +#### `ATTACHMENT_COMPRESSION` + +When the attachment_storage option is `ATTACHMENT_STORAGE=files`, this option controls the compression level for the attachments. Higher compression levels will compress data better, but at the cost of CPU usage. Lower compression levels will require less CPU but will compress data less. Setting the compression level to 0 will effectively disable compression completely. Changing the compression level, or switching it on or off, will not affect any existing attachments, and will remain accessible as normal. Set to 0 to disable compression completely. The maximum compression level is 9. Default: `ATTACHMENT_COMPRESSION=6` + +### SQL Database initialization + +When creating the SQL container you can use environment variables to initiate it. For example, `MYSQL_ROOT_PASSWORD=topsecret`, `MYSQL_DATABASE=kopano`, `MYSQL_USER=kopano` and `MYSQL_PASSWORD=verysecret`. + +## Persistent data + +There are at least three directories which should be considered mounted; the configuration files, `/etc/kopano`, the mail attachments, if they are kept in files, `/var/lib/kopano/attachments` and the active sync device states, if they are kept in files, `/var/lib/z-push`. + +## Authentication `USER_PLUGIN` + +Kopano supports three different ways to manage user authentication. Use the `USER_PLUGIN` environment variable to select the source of the user base. Possible values are: `db` (default), `ldap` and `unix`. + + `db`: Retrieve the users from the Kopano database. Use the kopano-admin tool to create users and groups. There are no additional settings for this plugin. + +`ldap`: Retrieve the users and groups information from an LDAP server. All additional LDAP settings are needed see below + +`unix`: Retrieve the users and groups information from the Linux password files. This option is probably not interesting here. + +### LDAP authentication + +An LDAP server with user accounts configured to be used with Kopano is needed, but how to set one up is out of our scope here, instead see: [Kopano Knowledge Base/Install and optimize OpenLDAP for use with Kopano Groupware Core](https://kb.kopano.io/display/WIKI/Install+and+optimize+OpenLDAP+for+use+with+Kopano+Groupware+Core). + +Once the LDAP server is up and running, the `mlan/kopano` container can be configured to use it using environment variables. In addition to the variables discussed below also set `USER_PLUGIN=ldap`. + +#### `LDAP_HOST`, `LDAP_PORT`, `LDAP_PROTOCOL` + +These directives specify a single LDAP server to use. Defaults: `LDAP_HOST=localhost`, `LDAP_PORT=389`, `LDAP_PROTOCOL=ldap` + +#### `LDAP_SEARCH_BASE` + +This is the subtree entry where all objects are defined in the LDAP server. Default: `LDAP_SEARCH_BASE=dc=kopano,dc=com` + +#### `LDAP_USER_TYPE_ATTRIBUTE_VALUE` + +This variable determines what defines a valid Kopano user. Default: `LDAP_USER_TYPE_ATTRIBUTE_VALUE=posixAccount` + +#### `LDAP_GROUP_TYPE_ATTRIBUTE_VALUE` + +This variable determines what defines a valid Kopano group. Default: `LDAP_GROUP_TYPE_ATTRIBUTE_VALUE=posixGroup` + +#### `LDAP_USER_SEARCH_FILTER` + +Adds an extra filter to the user search. Default `LDAP_USER_SEARCH_FILTER=` + +Hint: Use the `kopanoAccount` attribute in the filter to differentiate between non-kopano and kopano users. + +### Enabling IMAP and POP3 `DISABLED_FEATURES` + +By default the `imap` and `pop3` services are disabled for all users. You can set the environment variable `DISABLED_FEATURES=` to enable both `imap` and `pop3`. In this list you can disable certain features for users. This list is space separated, and currently may contain the following features: `imap`, `pop3`. Default: `DISABLED_FEATURES=imap pop3` + +### Logging `LOG_LEVEL` + +The level of output for logging in the range from 0 to 6. 0 means no logging, 1 for critical messages only, 2 for error or worse, 3 for warning or worse, 4 for notice or worse, 5 for info or worse, 6 debug. Default: `LOG_LEVEL=3` + +## Custom themes + +You can easily customize the Kopano WebApp see [New! JSON themes in Kopano WebApp](https://kopano.com/blog/new-json-themes-in-kopano-webapp/). Once you have the files you can install them in your docker container using the receipt below, where we assume that the container name is `mail-app` and that the directory `mytheme` contains the `theme.json` and the other file defining the theme. + +```bash +$ docker cp mytheme/. mail-app:/etc/kopano/theme/Custom +$ docker exec mail-app chown -R root:root /etc/kopano/theme +$ docker exec mail-app conf 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. + +### Mail transfer agent interaction + +Environment variables can be used to configure where Kopano find the Mail Transfer Agent, such as Postfix. Likewise the Mail Transfer Agent need to know where to forward emails to. + +#### `SMTP_SERVER` + +Hostname or IP address of the outgoing SMTP server. This server needs to relay mail for your server. Default: `SMTP_SERVER=localhost` + +#### `SMTP_PORT` + +TCP Port number for smtp_server. Default: `SMTP_PORT=25` + +### Configuring postfix + +The Kopano server listens to the port 2003 and expect the LMTP protocol. For Postfix you can define `DAGENT_TRANSPORT=lmtp:mail-app:2003` assuming the `mlan/kopano` container is named `mail-app` \ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..5132fb0 --- /dev/null +++ b/TODO.md @@ -0,0 +1,20 @@ +# To Do + +## 1) apache2 runit script not working properly +See: https://github.com/phusion/baseimage-docker/issues/271 + +## 2) Improve healthcheck +Verify the user anonymously. +```bash +ldapsearch -h dockerhost -xLLL -b dc=circuit-factory,dc=com '(kopanoAccount=1)' +``` + +Check if kopano can get the user from LDAP +```bash +kopano-admin -l +``` +check that apache and mysql is running +```bash +apache2ctl status +mysqlcheck -A +``` diff --git a/assets/conf b/assets/conf new file mode 100755 index 0000000..08f906e --- /dev/null +++ b/assets/conf @@ -0,0 +1,147 @@ +#!/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/assets/dpkg-version.sh b/assets/dpkg-version.sh new file mode 100755 index 0000000..6747914 --- /dev/null +++ b/assets/dpkg-version.sh @@ -0,0 +1,3 @@ +#!/bin/bash +pkg=${1-kopano-server} +dpkg -l | sed -nr 's/.*'"$pkg"'\s+([^ ]+).*/\1/p' diff --git a/assets/entrypoint.sh b/assets/entrypoint.sh new file mode 100755 index 0000000..f6ec2bc --- /dev/null +++ b/assets/entrypoint.sh @@ -0,0 +1,106 @@ +#!/bin/bash + +# +# config +# + +docker_build_runit_root=${docker_build_runit_root-/etc/service} +kopano_cfg_dir=/etc/kopano +zpush_cfg_dir=/usr/share/z-push +server_cfg_file=$kopano_cfg_dir/server.cfg +ldap_cfg_file=$kopano_cfg_dir/ldap.cfg +spooler_cfg_file=$kopano_cfg_dir/spooler.cfg +zpush_cfg_file=$zpush_cfg_dir/config.php +sqlstate_cfg_file=$zpush_cfg_dir/backend/sqlstatemachine/config.php + +# +# define environment variables +# + +server_env_vars="MYSQL_HOST MYSQL_PORT MYSQL_DATABASE MYSQL_USER MYSQL_PASSWORD DISABLED_FEATURES USER_PLUGIN LOG_LEVEL" +ldap_env_vars="LDAP_HOST LDAP_PORT LDAP_PROTOCOL LDAP_SEARCH_BASE LDAP_USER_TYPE_ATTRIBUTE_VALUE LDAP_GROUP_TYPE_ATTRIBUTE_VALUE LDAP_USER_SEARCH_FILTER" +spooler_env_vars="SMTP_SERVER SMTP_PORT" +zpush_env_vars="TIMEZONE USE_CUSTOM_REMOTE_IP_HEADER USE_FULLEMAIL_FOR_LOGIN STATE_MACHINE STATE_DIR LOGBACKEND LOGLEVEL LOGAUTHFAIL LOG_SYSLOG_PROGRAM LOG_SYSLOG_FACILITY SYNC_CONFLICT_DEFAULT PING_INTERVAL FILEAS_ORDER SYNC_MAX_ITEMS UNSET_UNDEFINED_PROPERTIES ALLOW_WEBSERVICE_USERS_ACCESS USE_PARTIAL_FOLDERSYNC" +sqlstate_env_vars="STATE_SQL_ENGINE STATE_SQL_SERVER STATE_SQL_PORT STATE_SQL_DATABASE STATE_SQL_USER STATE_SQL_PASSWORD STATE_SQL_OPTIONS" + +# +# Define helpers +# + +define_formats() { + name=$(basename $0) + f_norm="\e[0m" + f_bold="\e[1m" + f_red="\e[91m" + f_green="\e[92m" + f_yellow="\e[93m" +} + +inform() { + echo -e "$f_bold${f_green}INFO ($name)${f_norm} $@" +} + + +# +# kopano now installs without any cfg files, so we just write custom values +# into their target cfg file +# + +_kopano_cfg_gen() { + # do not touch existing cfg files + 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" + echo ${env_var,,} = ${!env_var} >> $cfg_file + fi + done + fi +} + +_php_cfg_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" + 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 +} + +php_cfg() { + _php_cfg_gen $zpush_cfg_file $zpush_env_vars + _php_cfg_gen $sqlstate_cfg_file $sqlstate_env_vars +} + +loglevel() { + if [ -n "$SYSLOG_LEVEL" -a $SYSLOG_LEVEL -ne 4 ]; then + setup-runit.sh "syslogd -n -O /dev/stdout -l $SYSLOG_LEVEL" + fi +} + +# +# run +# + +define_formats +kopano_cfg +php_cfg +loglevel + +exec 2>&1 +exec runsvdir -P $docker_build_runit_root + diff --git a/assets/healthcheck.sh b/assets/healthcheck.sh new file mode 100755 index 0000000..3cdfb5d --- /dev/null +++ b/assets/healthcheck.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# +# health check +# +# uses the runit tool to check if a service that is configured to run is down. +# This is a quite limited check. +# + +docker_build_runit_root=${docker_build_runit_root-/etc/service} + +if sv status $(ls $docker_build_runit_root) | grep "normally up, want up"; then + exit 1 +else + exit 0 +fi diff --git a/assets/kopano-webaddr.sh b/assets/kopano-webaddr.sh new file mode 100755 index 0000000..649f935 --- /dev/null +++ b/assets/kopano-webaddr.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# +# defaults +# + +_webroot="https://download.kopano.io/community" +_debroot="http://repo.z-hub.io/z-push:" +_component="core" +_stage="final" +_dist="debian" +_rel="8" +_arch="amd64" + +# +# define helpers +# + +escape_dot() { sed 's/\./\\\./g' ;} +captialize() { echo "$@" | sed 's/[^ _-]*/\u&/g' ;} +decimalise() { echo "$@" | sed '/\./!s/$/\.0/g' ;} +get_version() { sed -nr 's/.*'$1'-([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' ;} + +webaddr_kopano() { + # Find weblinks to latest Kopano packages + local component="${1-$_component}" + local webroot="${2-$_webroot}" + local dist=$(captialize "${3-$_dist}") + local rel=$(decimalise "${4-$_rel}" | escape_dot) + local arch="${5-$_arch}" + file=$(curl -s -S "$webroot/$component:/" | \ + sed -nr 's/.*('$component'-.*-'$dist'_'$rel'-'$arch'\.tar\.gz).*/\1/p') + echo "$webroot/$component:/$file" +} + +debaddr_zpush() { + local stage="${1-$_stage}" + local debroot="${2-$_debroot}" + local dist=$(captialize "${3-$_dist}") + local rel=$(decimalise "${4-$_rel}") + echo "$debroot/$stage/${dist}_${rel}" +} + +# +# run +# + +case $1 in + -V|--version) + shift + webaddr_kopano $@ | get_version + ;; + -VV) + shift + _core_ver=$(webaddr_kopano core | get_version core) + _arch="all" + _webapp_ver=$(webaddr_kopano webapp | get_version webapp) + echo "$_core_ver-$_webapp_ver" + ;; + -d|--deb) + shift + debaddr_zpush $@ + ;; + *) + webaddr_kopano $@ + ;; +esac diff --git a/assets/setup-runit.sh b/assets/setup-runit.sh new file mode 100755 index 0000000..7bca0e3 --- /dev/null +++ b/assets/setup-runit.sh @@ -0,0 +1,98 @@ +#!/bin/sh + +# use /etc/service if $docker_build_runit_root not already defined +docker_build_runit_root=${docker_build_runit_root-/etc/service} +#docker_build_svlog_root=${docker_build_svlog_root-/var/log/sv} + +# +# Define helpers +# + +define_formats() { + name=$(basename $0) + f_norm="\e[0m" + f_bold="\e[1m" + f_red="\e[91m" + f_green="\e[92m" + f_yellow="\e[93m" +} + +inform() { + echo "$f_bold${f_green}INFO ($name)${f_norm} $@" +} + +# +# initialize runit services +# + +init_service() { + # create runit 'run' script for service + # if service is part of kopano suite alos make the 'run' script + # delete lingering pid files, which appears to happen to kopano-search + local cmd="$1" + shift + local runit_dir=$docker_build_runit_root/${cmd##*/} + local svlog_dir=$docker_build_svlog_root/${cmd##*/} + local clear_pids_str= + if echo $cmd | grep kopano - >/dev/null; then + clear_pids_str="rm -f /var/run/kopano/${cmd#*kopano-}.pid*" + fi + cmd=$(which $cmd) + if [ ! -z "$cmd" ]; then + mkdir -p $runit_dir + cat <<-!cat > $runit_dir/run + #!/bin/sh -e + #exec 2>&1 + $(echo $clear_pids_str) + exec $cmd $@ + !cat + chmod +x $runit_dir/run + inform "runit configured to run: $cmd $@" + if [ -n "$docker_build_svlog_root" ]; then + mkdir -p $runit_dir/log $svlog_dir + cat <<-!cat > $runit_dir/log/run + #!/bin/sh + exec svlogd -tt $svlog_dir + !cat + chmod +x $runit_dir/log/run + fi + fi + } + +init_services() { + for cmd in "$@" ; do + init_service $cmd + done +} + +down_service() { + local cmd="$1" + touch $docker_build_runit_root/$cmd/down + inform "runit configured to down: $cmd" +} + +down_services() { + for cmd in "$@" ; do + down_service $cmd + done + } + +setup_services() { + case "$1" in + -d|--down) + shift + init_services "$@" + down_services "$@" + ;; + *) + init_services "$@" + ;; + esac +} + +# +# run +# + +define_formats +setup_services "$@" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d479e27 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,150 @@ +version: '3.7' + +services: + mail-app: + image: mlan/kopano:8.7.80-3.5.2 + restart: unless-stopped + networks: + - proxy + - backend + labels: + - traefik.enable=true + - traefik.frontend.rule=Host:mail.${DOMAIN-docker.localhost} + - traefik.docker.network=${COMPOSE_PROJECT_NAME}_proxy + - traefik.port=80 + depends_on: + - auth + - mail-db + - mail-mta + environment: + - USER_PLUGIN=ldap + - LDAP_HOST=auth + - MYSQL_HOST=mail-db + - SMTP_SERVER=mail-mta + - LDAP_SEARCH_BASE=${LDAP_BASE} + - LDAP_USER_TYPE_ATTRIBUTE_VALUE=kopano-user + - LDAP_GROUP_TYPE_ATTRIBUTE_VALUE=kopano-group + - LDAP_USER_SEARCH_FILTER=(kopanoAccount=1) + - SYSLOG_LEVEL=4 + env_file: + - .init.env + volumes: + - mail-conf:/etc/kopano + - mail-atch:/var/lib/kopano/attachments + - mail-sync:/var/lib/z-push + + mail-mta: + image: mlan/postfix-amavis:3.8 + restart: unless-stopped + hostname: ${MAIL_SRV-mx}.${MAIL_DOMAIN-docker.localhost} + networks: + - backend + ports: + - "25:25" + labels: + - traefik.enable=true + - traefik.frontend.rule=Host:${MAIL_SRV-mx}.${MAIL_DOMAIN-docker.localhost} + - traefik.docker.network=${COMPOSE_PROJECT_NAME}_proxy + - traefik.port=80 + depends_on: + - auth + environment: + - MESSAGE_SIZE_LIMIT=${MESSAGE_SIZE_LIMIT-25600000} + - LDAP_HOST=auth + - DAGENT_TRANSPORT=lmtp:mail-app:2003 + - SMTP_RELAY_HOSTAUTH=${SMTP_RELAY_HOSTAUTH} + - SMTP_TLS_SECURITY_LEVEL=${SMTP_TLS_SECURITY_LEVEL-} + - SMTP_TLS_WRAPPERMODE=${SMTP_TLS_WRAPPERMODE-no} + - LDAP_USER_BASE=${LDAP_USEROU},${LDAP_BASE} + - LDAP_GROUP_BASE=${LDAP_GROUPOU},${LDAP_BASE} + - LDAP_QUERY_FILTER_USER=(&(kopanoAccount=1)(mail=%s)) + - LDAP_QUERY_FILTER_ALIAS=(&(kopanoAccount=1)(kopanoAliases=%s)) + - LDAP_QUERY_FILTER_GROUP=(&(objectclass=kopano-group)(mail=%s)) + - LDAP_QUERY_FILTER_EXPAND=(&(objectclass=kopano-user)(uid=%s)) + - DKIM_SELECTOR=${DKIM_SELECTOR-default} + - SYSLOG_LEVEL=5 + env_file: + - .init.env + volumes: + - mail-mta:/var + - proxy-acme:/acme + + mail-db: + image: mariadb + restart: unless-stopped + command: ['--log_warnings=1'] + networks: + - backend + environment: + - LANG=C.UTF-8 + env_file: + - .init.env + volumes: + - mail-db:/var/lib/mysql + + auth: + image: mlan/openldap:1.0 + restart: unless-stopped + networks: + - backend + environment: + - LDAP_LOGLEVEL=parse + volumes: + - auth-conf:/srv/conf + - auth-data:/srv/data + + proxy: + image: traefik:alpine + restart: unless-stopped + command: + - "--api" + - "--docker" + - "--defaultentrypoints=http,https" + - "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https" + - "--entrypoints=Name:https Address::443 TLS" + - "--retry" + - "--docker.domain=${DOMAIN-docker.localhost}" + - "--docker.exposedbydefault=false" + - "--docker.watch=true" + - "--acme" + - "--acme.email=${CERTMASTER-certmaster}@${DOMAIN-docker.localhost}" + - "--acme.entrypoint=https" + - "--acme.onhostrule=true" + - "--acme.storage=/acme/acme.json" + - "--acme.httpchallenge" + - "--acme.httpchallenge.entrypoint=http" + - "--loglevel=ERROR" + cap_drop: + - all + cap_add: + - net_bind_service + networks: + - proxy + ports: + - "80:80" # The HTTP port + - "443:443" # The HTTPS port + labels: + - traefik.enable=true + - traefik.docker.network=${COMPOSE_PROJECT_NAME}_proxy + - traefik.port=8080 + - traefik.frontend.passHostHeader=true + - traefik.frontend.rule=Host:monitor.${DOMAIN-docker.localhost} + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - proxy-acme:/acme + - /dev/null:/traefik.toml + +networks: + proxy: + backend: + +volumes: + proxy-acme: + mail-conf: + mail-atch: + mail-db: + mail-mta: + mail-sync: + auth-conf: + auth-data: +