Compare commits
69 Commits
Author | SHA1 | Date |
---|---|---|
|
fe6566c427 | |
|
313d954381 | |
|
08a3e3ec9b | |
|
0fb026a934 | |
|
bd88becc7c | |
|
4579fd70ad | |
|
70b6ab3a9f | |
|
e0fc560dd3 | |
|
36cd823be8 | |
|
6e32101634 | |
|
b9c2529745 | |
|
f82c64cd47 | |
|
39fe4e82a1 | |
|
3b44ca9122 | |
|
c5b37073b6 | |
|
4ccda2a36c | |
|
8e9a9bcf67 | |
|
7ff783c870 | |
|
b76d723d04 | |
|
56857c5528 | |
|
611d4ae694 | |
|
f13d2d2329 | |
|
9d7ed67165 | |
|
0451ace4af | |
|
dc275ea073 | |
|
f9719f8a2b | |
|
90b8433d3c | |
|
737c33dea2 | |
|
c2a2733f7e | |
|
c92a4c0b68 | |
|
230e6953fd | |
|
362f4e3865 | |
|
4b8bc2b445 | |
|
bc80ab04a3 | |
|
3a357ee94a | |
|
8b252367b9 | |
|
162e9ca256 | |
|
0970ecc655 | |
|
7309eba0e5 | |
|
fe404fc0cb | |
|
2acaf05d93 | |
|
7336125c68 | |
|
58da6e67dd | |
|
34247239b5 | |
|
94e0581fbd | |
|
9e1906db58 | |
|
cbf0fdb5e2 | |
|
d9d32e301b | |
|
8ec9fbefa7 | |
|
b0ba6568dd | |
|
40c0b85046 | |
|
8bc08ee49e | |
|
fbbe374f2c | |
|
230e3ac45b | |
|
84ee56939d | |
|
b66b9c696b | |
|
d94a64104e | |
|
cb63180d43 | |
|
5611e074e3 | |
|
e2c9b93584 | |
|
a276c55304 | |
|
161ae9b5fd | |
|
68d465f9a6 | |
|
5b98e4fdfb | |
|
b809f74369 | |
|
d8dd60af4e | |
|
4b0fafbb22 | |
|
f83e95e3aa | |
|
902a893205 |
|
@ -2,5 +2,8 @@
|
|||
.gitignore
|
||||
.github
|
||||
.gitattributes
|
||||
READMETEMPLATE.md
|
||||
README.md
|
||||
src/notused
|
||||
demo
|
||||
*.md
|
||||
local*
|
||||
*private
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
deb
|
||||
tmp
|
||||
*.swp
|
||||
local_*
|
||||
local*
|
||||
*private
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
language: ruby
|
||||
os: linux
|
||||
dist: bionic
|
||||
services: docker
|
||||
install: make build-all
|
||||
before_script: sudo apt-get install -y ldap-utils openssl jq
|
||||
script:
|
||||
- make test-pull
|
||||
- make test-all
|
|
@ -0,0 +1,158 @@
|
|||
# 1.3.1
|
||||
|
||||
- [docker](Dockerfile) Update Z-Push package URL.
|
||||
- [kopano](src/kopano) Now use ubuntu:20.04 in `kopano-webaddr.sh`.
|
||||
- [docker](ROADMAP.md) Update roadmap.
|
||||
|
||||
# 1.3.0
|
||||
|
||||
- [docker](Dockerfile) Now use ubuntu:20.04 as base image.
|
||||
- [docker](Makefile) Don't set BLD_ARG in Makefile.
|
||||
- [kopano](src/kopano/plugin/movetopublicldap.py) Need explicitly to set `allow_no_value=True` due to update in `configparser.3.8.py`.
|
||||
- [demo](demo) Update to `mta-apk_list` target.
|
||||
|
||||
# 1.2.9
|
||||
|
||||
- [kopano](src/kopano) Bug in kopano-dagent out-of-office now fixed.
|
||||
- [demo](demo) Make demo less dependent on that curl and firefox beeing installed on the host.
|
||||
- [demo](demo) Don't expose any ports on the host, avoiding potential conflicts.
|
||||
|
||||
# 1.2.8
|
||||
|
||||
- [test](test) Update to use `mlan/openldap:2`.
|
||||
- [demo](demo) Update to use `mlan/openldap:2`.
|
||||
|
||||
# 1.2.7
|
||||
|
||||
- [kopano](src/kopano/plugin/movetopublicldap.py) Add LDAP support to the [Move to public](https://documentation.kopano.io/kopanocore_administrator_manual/special_kc_configurations.html#move-to-public ) kopano-dagent python plugin.
|
||||
- [kopano](src/kopano/plugin/movetopublicldap.py) Add sample config file for the kopano-dagent python plugin movetopublicldap.
|
||||
|
||||
# 1.2.6
|
||||
|
||||
- [docker](src/docker/bin/docker-config.sh) Allow the crontab support to work also when the file `/etc/kopano/docker-crontab` is missing.
|
||||
|
||||
# 1.2.5
|
||||
|
||||
- [docker](Dockerfile) Install the [kopano archiver](https://documentation.kopano.io/kopano_archiver_manual/) in target `core`.
|
||||
- [docker](Dockerfile) Install the smime webapp plugin providing [S/MIME](https://kopano.com/blog/s-mime-plugin-description/).
|
||||
- [docker](Dockerfile) Install the mdm webapp plugin providing [Mobile Device Management](https://documentation.kopano.io/webapp_mdm_manual/).
|
||||
- [demo](demo) Add [S/MIME](https://kopano.com/blog/s-mime-plugin-description/) cert generation.
|
||||
- [kopano](src/kopano/entry.d/10-kopano-common) Fix bug in `kopano_apply_envvars_php()`.
|
||||
- [kopano](src/kopano/entry.d/10-kopano-common) Allow module based parameter names.
|
||||
- [docker](src/docker/bin/docker-config.sh) Add provision to set up crontab using envvars.
|
||||
- [docker](README.md) Updated documentation.
|
||||
- [kopano](src/kopano) `MIGRATE_CONFIG=2` make sure WebApp plugins have configuration files in place.
|
||||
- [docker](Dockerfile) Make a copy of the kopano-webapp config files.
|
||||
|
||||
# 1.2.4
|
||||
|
||||
- [kopano](src/kopano) Now, use man pages and sample config files to find valid keys to match envvar.
|
||||
- [kopano](src/kopano) Load Kopano LDAP attributes using `LDAP_PROPMAP=`.
|
||||
|
||||
# 1.2.3
|
||||
|
||||
- [kopano](src/kopano) Adding support for secure IMAPS, POP3S and ICALS.
|
||||
- [kopano](src/kopano) Now generate self-signed certs if needed.
|
||||
- [demo](demo) Enable IMAPS, POP3S, ICALS and SMTPS in demo.
|
||||
- [acme](src/acme) Added module providing Let's encrypt TLS certificates using ACME.
|
||||
- [acme](src/acme) Fixed ACME module shell (ash and bash) portability.
|
||||
- [test](test/Makefile) Arrange build tests.
|
||||
- [test](test/Makefile) Add SSL build tests.
|
||||
- [travis-ci](.travis.yml) Travis CI now run test-all.
|
||||
- [docker](src/docker/bin/docker-config.sh) Generalized `dc_cond_chown()`.
|
||||
- [kopano](src/kopano) Updated documentation and bug fix of support for secure IMAPS, POP3S and ICALS.
|
||||
- [kopano](src/kopano) New, `MIGRATE_CONFIG=all` tries to make configs compatible with new versions.
|
||||
- [test](test/Makefile) Some sleep time in between db and app speeds up overall test times.
|
||||
- [travis-ci](.travis.yml) Fixed test errors on Travis-CI, which was due to long SQL db initialization.
|
||||
|
||||
# 1.2.2
|
||||
|
||||
- [kopano](src/kopano) Adding support for gateway / IMAP iCAL search configuration via envvars in, `50-kopano-apply-envvars`.
|
||||
- [kopano](src/kopano) Fixed old bug where set but empty environment variable was ignored by `50-kopano-apply-envvars`.
|
||||
- [demo](demo) Enable IMAP POP3 and CalDAV/iCAL in demo.
|
||||
- [docker](Dockerfile) Update Kopano services.
|
||||
- [docker](README.md) Added sections `Mail client configuration` and `Implementation`.
|
||||
- [docker](Dockerfile) Remove the `debugtools` build target. There already exists an `app-debugtools` target in the [`demo/Makefile`](demo/Makefile).
|
||||
- [docker](Dockerfile) Now use an unlock file, which is removed after a virgin container has been configured. This locks the configuration on restarts unless `FORCE_CONFIG` is given.
|
||||
- [docker](Dockerfile) To allow HTTP access to webapp we need `define("SECURE_COOKIES", false)` in `/etc/kopano/webapp/config.php`. Earlier is was `define("INSECURE_COOKIES", true)`.
|
||||
|
||||
# 1.2.1
|
||||
|
||||
- [docker](Dockerfile) The Kopano installation now (version 10.0.6) populate all example-config files in /etc/kopano. This breaks our configuration, so we need to remove them. They can still be found here /usr/share/doc/kopano/example-config.
|
||||
|
||||
# 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.
|
||||
- [demo](demo) Made service names shorter.
|
||||
|
||||
# 1.1.7
|
||||
|
||||
- [docker](Dockerfile) Configure kopano-spamd to start with the force-remove-lingering-pid switch.
|
||||
- [demo](demo) Now with 10.0.3 LDAP users get their share created, again.
|
||||
|
||||
# 1.1.6
|
||||
|
||||
- [docker](Dockerfile) Use syslogd, don't write to /var/log/apache2/other_vhosts_access.log.
|
||||
- [docker](Dockerfile) No need for python to write bytecode to container. Disabling that.
|
||||
- [repo](src) Separate source code in by which service it belongs to.
|
||||
- [kopano](src/kopano) Configure kopano-spamd.
|
||||
- [kopano](src/kopano) Workaround kopano-spamd bug: /var/lib/kopano/spamd/ham created with wrong permissions.
|
||||
|
||||
# 1.1.5
|
||||
|
||||
- [demo](demo) Use host timezone by mounting /etc/localtime.
|
||||
- [demo](demo) Since 10.0.1 LDAP users don't get their share created, so `make init` now does that.
|
||||
|
||||
# 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 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 `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.
|
||||
- Renamed some build variables, e.g., `DOCKER_RUNSV_DIR` (was `docker_build_runit_root`).
|
||||
- Cleaning up `Makefile`
|
||||
- Added more debug functionality in `demo/Makefile`
|
||||
|
||||
# 1.1.2
|
||||
|
||||
- Update `Dockerfile` so that is works also for Debian 9
|
||||
- Update `kopano-webaddr.sh` now that we do not have builds for Debian 8
|
||||
- Updated demo
|
||||
|
||||
# 1.1.1
|
||||
|
||||
- Make sure the .env settings are honored also for MYSQL
|
||||
|
||||
# 1.1.0
|
||||
|
||||
- Reversed tag naming scheme. now `full-8.7.80-3.5.2` instead of ~~8.7.80-3.5.2-full~~
|
||||
- Demo based on `docker-compose.yml` and `Makefile` files
|
||||
- Check and fix file attributes in the `/var/lib/kopano/attachments` directory
|
||||
|
||||
# 1.0.0
|
||||
|
||||
- Groupware server [Kopano WebApp](https://kopano.io/)
|
||||
- ActiveSync server [Z-Push](http://z-push.org/)
|
||||
- Multi-staged build providing the images `full`, `debugtools` and `core`
|
||||
- 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
|
||||
|
238
Dockerfile
238
Dockerfile
|
@ -1,22 +1,45 @@
|
|||
#
|
||||
# build arguments, amd64 is the default
|
||||
#
|
||||
ARG DIST=ubuntu
|
||||
ARG REL=18.04
|
||||
ARG ARCH=amd64
|
||||
ARG REL=20.04
|
||||
ARG ARCH
|
||||
|
||||
|
||||
|
||||
FROM $ARCH/$DIST:$REL AS base
|
||||
FROM ${ARCH:+$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
|
||||
PYTHONDONTWRITEBYTECODE=PleaseNoPyCache \
|
||||
SVDIR=/etc/service \
|
||||
DOCKER_BIN_DIR=/usr/local/bin \
|
||||
DOCKER_ENTRY_DIR=/etc/docker/entry.d \
|
||||
DOCKER_EXIT_DIR=/etc/docker/exit.d \
|
||||
DOCKER_CRONTAB_FILE=/etc/kopano/docker-crontab \
|
||||
DOCKER_CRONTAB_DIR=/etc/cron.d \
|
||||
DOCKER_CONF_DIR1=/etc/kopano \
|
||||
DOCKER_SMPL_DIR1=/usr/share/doc/kopano/example-config \
|
||||
DOCKER_PLUG_DIR=/usr/share/kopano-dagent/python/plugins \
|
||||
DOCKER_CONF_DIR2=/usr/share/z-push \
|
||||
DOCKER_APPL_LIB=/var/lib/kopano \
|
||||
DOCKER_APPL_SSL_DIR=/etc/kopano/ssl \
|
||||
DOCKER_ACME_SSL_DIR=/etc/ssl/acme \
|
||||
KOPANO_SPAMD_LIB=/var/lib/kopano/spamd \
|
||||
DOCKER_APPL_RUNAS=kopano \
|
||||
DOCKER_BUILD_DEB_DIR=/tmp/deb \
|
||||
DOCKER_BUILD_PASSES=1 \
|
||||
DOCKER_UNLOCK_FILE=/etc/kopano/.docker.unlock \
|
||||
SYSLOG_OPTIONS='-S' \
|
||||
SYSLOG_LEVEL=5
|
||||
#
|
||||
# Copy helpers
|
||||
# Copy utility scripts including docker-entrypoint.sh to image
|
||||
#
|
||||
COPY assets/setup-runit.sh /usr/local/bin/
|
||||
COPY src/*/bin $DOCKER_BIN_DIR/
|
||||
COPY src/*/entry.d $DOCKER_ENTRY_DIR/
|
||||
COPY src/*/exit.d $DOCKER_EXIT_DIR/
|
||||
COPY src/*/config $DOCKER_CONF_DIR1/
|
||||
COPY src/*/plugin $DOCKER_PLUG_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 \
|
||||
|
@ -27,7 +50,14 @@ RUN apt-get update && apt-get install --yes --no-install-recommends \
|
|||
ca-certificates \
|
||||
tar \
|
||||
gnupg \
|
||||
&& setup-runit.sh "syslogd -n -O /dev/stdout -l $SYSLOG_LEVEL"
|
||||
jq \
|
||||
inotify-tools \
|
||||
cron \
|
||||
&& ln -s $DOCKER_CRONTAB_FILE $DOCKER_CRONTAB_DIR \
|
||||
&& docker-service.sh \
|
||||
"syslogd -nO- -l$SYSLOG_LEVEL $SYSLOG_OPTIONS" \
|
||||
"cron -f"
|
||||
# "cron -f -L 4"
|
||||
|
||||
|
||||
|
||||
|
@ -39,124 +69,147 @@ 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
|
||||
RUN rm /etc/dpkg/dpkg.cfg.d/excludes \
|
||||
#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 \
|
||||
&& apt-get install --yes --no-install-recommends \
|
||||
man \
|
||||
manpages \
|
||||
bash-completion \
|
||||
&& rm -r /var/lib/apt/lists/*
|
||||
&& rm -r /var/lib/apt/lists/*
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Kopano-core
|
||||
#
|
||||
FROM base-man AS kopano-core
|
||||
FROM base-man AS core
|
||||
#
|
||||
# build arguments
|
||||
# build arguments, amd64 is the default
|
||||
#
|
||||
ARG DIST
|
||||
ARG REL
|
||||
ARG ARCH
|
||||
ARG ARCH=amd64
|
||||
#
|
||||
# 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/
|
||||
SVDIR=/etc/service \
|
||||
DOCKER_BIN_DIR=/usr/local/bin \
|
||||
LMTP_LISTEN=*:2003 \
|
||||
SA_GROUP=kopano \
|
||||
DOCKER_BUILD_DEB_DIR=/tmp/deb \
|
||||
DOCKER_BUILD_PASSES=1
|
||||
#
|
||||
# Install kopano-core
|
||||
#
|
||||
RUN mkdir -p $docker_build_deb_dir \
|
||||
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 \
|
||||
&& curl $webaddr | tar -xzC $DOCKER_BUILD_DEB_DIR \
|
||||
&& webaddr=$(kopano-webaddr.sh archiver \
|
||||
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 \
|
||||
&& 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 \
|
||||
&& apt-get install --yes --no-install-recommends python3-ldap \
|
||||
&& mkdir -p /var/lib/kopano/attachments && chown $DOCKER_APPL_RUNAS: /var/lib/kopano/attachments \
|
||||
&& mkdir -p $DOCKER_APPL_SSL_DIR \
|
||||
&& mkdir -p $DOCKER_ACME_SSL_DIR \
|
||||
&& mkdir -p $KOPANO_SPAMD_LIB/ham && chown $DOCKER_APPL_RUNAS: $KOPANO_SPAMD_LIB/ham \
|
||||
&& rm -rf $DOCKER_BUILD_DEB_DIR \
|
||||
&& rm $DOCKER_CONF_DIR1/*.cfg \
|
||||
&& . docker-common.sh \
|
||||
&& . docker-config.sh \
|
||||
&& dc_comment /etc/ssl/openssl.cnf RANDFILE \
|
||||
&& docker-service.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"
|
||||
"kopano-gateway" \
|
||||
"kopano-ical" \
|
||||
"kopano-search" \
|
||||
"kopano-server" \
|
||||
"kopano-spooler" \
|
||||
"-f kopano-spamd" \
|
||||
"-d kopano-grapi serve" \
|
||||
"-d kopano-kapid serve --log-timestamp=false" \
|
||||
"-d kopano-konnectd serve --log-timestamp=false" \
|
||||
"-d kopano-monitor" \
|
||||
&& echo "This file unlocks the configuration, so it will be deleted after initialization." > $DOCKER_UNLOCK_FILE
|
||||
#
|
||||
# Have runit's runsvdir start all services
|
||||
#
|
||||
CMD runsvdir -P ${SVDIR}
|
||||
#
|
||||
# Entrypoint, how container is run
|
||||
#
|
||||
HEALTHCHECK --interval=5m --timeout=3s CMD healthcheck.sh
|
||||
ENTRYPOINT ["entrypoint.sh"]
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
#
|
||||
# Check if all services are running
|
||||
#
|
||||
HEALTHCHECK CMD sv status ${SVDIR}/*
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Kopano-webapp
|
||||
#
|
||||
FROM kopano-core AS kopano-core-webapp
|
||||
FROM core AS 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
|
||||
SVDIR=/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 \
|
||||
RUN apt-get install --yes --no-install-recommends apache2 libapache2-mod-php \
|
||||
&& 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 \
|
||||
&& curl $webaddr | tar -xzC $DOCKER_BUILD_DEB_DIR \
|
||||
&& webaddr=$(kopano-webaddr.sh mdm \
|
||||
https://download.kopano.io/community ${DIST} ${REL} all) \
|
||||
&& echo "$webaddr<->${DIST} ${REL} all<-" \
|
||||
&& curl $webaddr | tar -xzC $DOCKER_BUILD_DEB_DIR \
|
||||
&& webaddr=$(kopano-webaddr.sh smime \
|
||||
https://download.kopano.io/community ${DIST} ${REL} ${ARCH}) \
|
||||
&& 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 \
|
||||
&& 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' '<VirtualHost *:80>\\nDocumentRoot /usr/share/kopano-webapp' \
|
||||
&& . docker-common.sh \
|
||||
&& . docker-config.sh \
|
||||
&& dc_replace /etc/kopano/webapp/config.php 'define("SECURE_COOKIES", true);' 'define("SECURE_COOKIES", false);' \
|
||||
&& dc_replace /etc/apache2/sites-available/kopano-webapp.conf 'Alias /webapp /usr/share/kopano-webapp' '<VirtualHost *:80>\\nDocumentRoot /usr/share/kopano-webapp' \
|
||||
&& echo '</VirtualHost>' >> /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 \
|
||||
&& 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 \
|
||||
# && a2disconf other-vhosts-access-log \
|
||||
# && 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 "apache2ctl -D FOREGROUND -k start"
|
||||
&& rm -rf $DOCKER_BUILD_DEB_DIR \
|
||||
&& cp -r $DOCKER_CONF_DIR1/webapp $DOCKER_SMPL_DIR1 \
|
||||
&& docker-service.sh "-f -s /etc/apache2/envvars -q apache2 -DFOREGROUND -DNO_DETACH -k start"
|
||||
#
|
||||
# Ports
|
||||
#
|
||||
|
@ -167,55 +220,36 @@ EXPOSE 80 443
|
|||
#
|
||||
# Z-Push
|
||||
#
|
||||
FROM kopano-core-webapp AS kopano-full
|
||||
FROM core-webapp AS 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
|
||||
SVDIR=/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
|
||||
# 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})" \
|
||||
RUN debaddr="$(kopano-webaddr.sh --deb final https://download.kopano.io/zhub/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 \
|
||||
&& mkdir -p /var/lib/z-push && chown www-data: /var/lib/z-push \
|
||||
&& mkdir -p /var/log/z-push && chown 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"' '</IfModule>' \
|
||||
&& 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/*
|
||||
|
||||
|
||||
&& . 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"' '</IfModule>' \
|
||||
&& 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);'
|
||||
|
||||
|
|
92
Makefile
92
Makefile
|
@ -1,71 +1,55 @@
|
|||
# Makefile
|
||||
#
|
||||
# build
|
||||
#
|
||||
|
||||
-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)
|
||||
#BLD_ARG ?= --build-arg DIST=ubuntu --build-arg REL=20.04
|
||||
BLD_REPO ?= mlan/kopano
|
||||
BLD_VER ?= latest
|
||||
BLD_TGT ?= full
|
||||
|
||||
CNT_NAME ?= kopano-default
|
||||
CNT_PORT ?= -p 80:80
|
||||
CNT_ENV ?=
|
||||
CNT_VOL ?=
|
||||
SRC_CMD ?= src/kopano/bin/kopano-webaddr.sh -VV
|
||||
SRC_VER ?= $(shell $(SRC_CMD))
|
||||
|
||||
.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
|
||||
TST_REPO ?= $(BLD_REPO)
|
||||
TST_VER ?= $(BLD_VER)
|
||||
TST_ENV ?= -C test
|
||||
TST_TGTE ?= $(addprefix test-,all diff down env htop imap lmtp logs mail pop3 pull sh sv up)
|
||||
TST_TGTI ?= test_% test-up_%
|
||||
|
||||
build: Dockerfile
|
||||
docker build $(BLD_ARG) --target kopano-full -t $(IMG_REPO)\:$(IMG_VER) .
|
||||
export TST_REPO TST_VER
|
||||
|
||||
build-all: build-core build-full build-debugtools
|
||||
_version = $(if $(findstring $(BLD_TGT),$(1)),\
|
||||
$(if $(findstring latest,$(2)),latest $(1) $(SRC_VER) $(1)-$(SRC_VER),$(2) $(1)-$(2)),\
|
||||
$(if $(findstring latest,$(2)),$(1) $(1)-$(SRC_VER),$(1)-$(2)))
|
||||
|
||||
build-core: Dockerfile
|
||||
docker build $(BLD_ARG) --target kopano-core \
|
||||
-t $(IMG_REPO)\:$(IMG_VER)-core \
|
||||
-t $(IMG_REPO)\:latest-core .
|
||||
build-all: build_core build_full
|
||||
|
||||
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: build_$(BLD_TGT)
|
||||
|
||||
build-debugtools: Dockerfile
|
||||
docker build $(BLD_ARG) --target kopano-debugtools \
|
||||
-t $(IMG_REPO)\:$(IMG_VER)-debugtools \
|
||||
-t $(IMG_REPO)\:latest-debugtools .
|
||||
build_%: Dockerfile
|
||||
docker build $(BLD_ARG) --target $* \
|
||||
$(addprefix --tag $(BLD_REPO):,$(call _version,$*,$(BLD_VER))) .
|
||||
|
||||
version:
|
||||
$(SRC_CMD)
|
||||
|
||||
variables:
|
||||
make -pn | grep -A1 "^# makefile"| grep -v "^#\|^--" | sort | uniq
|
||||
|
||||
ps:
|
||||
docker ps -a
|
||||
|
||||
push:
|
||||
docker push $(IMG_REPO)\:$(IMG_VER)
|
||||
prune:
|
||||
docker image prune -f
|
||||
|
||||
shell:
|
||||
docker run --rm --name $(CNT_NAME)-$(CNT_INST) -i -t $(CNT_PORT) $(CNT_VOL) $(CNT_ENV) $(IMG_REPO)\:$(IMG_VER) /bin/bash
|
||||
clean:
|
||||
docker images | grep $(BLD_REPO) | awk '{print $$1 ":" $$2}' | uniq | xargs docker rmi
|
||||
|
||||
exec:
|
||||
docker exec -it $(CNT_NAME) /bin/bash
|
||||
$(TST_TGTE):
|
||||
${MAKE} $(TST_ENV) $@
|
||||
|
||||
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
|
||||
$(TST_TGTI):
|
||||
${MAKE} $(TST_ENV) $@
|
||||
|
|
681
README.md
681
README.md
|
@ -1,217 +1,251 @@
|
|||
# The mlan/kopano repository
|
||||
# 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).
|
||||
This (non official) repository provides dockerized web mail service as well as Exchange ActiveSync (EAS), IMAP, POP3 and ICAL service (and their secure variants IMAPS, POP3S and ICALS). It is based on [Kopano](https://kopano.com) 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.
|
||||
|
||||
## Feature overview
|
||||
Hopefully this repository can be retired once the Kopano community make official images available. To learn more about this activity see [zokradonh/kopano-docker](https://github.com/zokradonh/kopano-docker).
|
||||
|
||||
Brief feature list follows below
|
||||
## Features
|
||||
|
||||
- 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`
|
||||
- [Kopano WebApp](https://kopano.io/) the main client to access all the features provided by Kopano core
|
||||
- [Exchange ActiveSync (EAS)](https://en.wikipedia.org/wiki/Exchange_ActiveSync) server [Z-Push](http://z-push.org/)
|
||||
- IMAP, POP3 and ICAL services provided by Kopano core
|
||||
- Secure protocols IMAPS, POP3S and ICALS
|
||||
- Hooks for integrating [Let’s Encrypt](https://letsencrypt.org/) LTS certificates using the reverse proxy [Traefik](https://docs.traefik.io/)
|
||||
- Multi-staged build providing the images `full` and `core`
|
||||
- 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
|
||||
- Built in utility script [run](src/docker/bin/run) helping configuring Kopano components, WebApp and Z-Push
|
||||
- [Move to public with LDAP lookup](#move-to-public-with-ldap-lookup)
|
||||
- [Crontab](https://en.wikipedia.org/wiki/Cron) support.
|
||||
- Health check
|
||||
- Hook for theming
|
||||
- Hook for theming
|
||||
- Demo based on `docker-compose.yml` and `Makefile` files
|
||||
|
||||
## Tags overview
|
||||
## Tags
|
||||
|
||||
The mlan/kopano repository contains a multi staged built. You select which build using the appropriate tag.
|
||||
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 version part of the tag is not based on the version of this repository. It is instead, based on the combined revision numbers of the nightly Kopano core and Kopano WebApp package suits that was available when building the images. 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 `full` or without ending contain Kopano core components, as well as, the Kopano webapp and z-push. The image with tag `debugtools` also contains some debug tools. The image with tag `core` contains the kopano-core components proving the server and imap, pop3 and ical access. The image with tag `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.
|
||||
The build part of the tag is one of `full` and `core`. The image with tag `full` contain Kopano core components, as well as, the Kopano WebApp and Z-Push. The image with tag `core` contains the Kopano core components proving the server and IMAP, POP3 and ICAL access, but no web access.
|
||||
|
||||
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`, `full`, `latest-full` and `8.7.80-3.5.2-full` all identify the same image.
|
||||
The tags `latest`, `full`, or `core` all reference the most recent builds.
|
||||
|
||||
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`, `full`, and `full-8.7.80-3.5.2` 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.
|
||||
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.
|
||||
|
||||
```bash
|
||||
docker run -d --name mail-app -p 80:80 mlan/kopano
|
||||
```
|
||||
Often you want to configure Kopano and its components. There are
|
||||
different methods available to achieve this. You can use the environment
|
||||
variables described below set in the shell before creating the container.
|
||||
These environment variables can also be explicitly given on
|
||||
the command line when creating the container. They can also be given in
|
||||
an `docker-compose.yml` file (and the `.env` file), see below. Moreover docker
|
||||
volumes or host directories with desired configuration files can be
|
||||
mounted in the container. And finally you can exec into a running container and modify configuration files directly.
|
||||
|
||||
The docker compose example below is used to demonstrate how to configure these services.
|
||||
|
||||
## Docker compose example
|
||||
|
||||
An example of how to configure an web mail server using docker compose is given below. It defines five services, `mail-app`, `mail-mta`, `mail-db`, `auth` and `proxy`, which are the web mail server, the mail transfer agent, the SQL database, authentication and reverse proxy respectively.
|
||||
An example of how to configure an web mail server using docker compose is given below. It defines 4 services, `app`, `mta`, `db` and `auth`, which are the web mail server, the mail transfer agent, the SQL database and LDAP authentication respectively.
|
||||
|
||||
```yaml
|
||||
version: '3.7'
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
mail-app:
|
||||
app:
|
||||
image: mlan/kopano
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- proxy
|
||||
- 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
|
||||
ports: # Expose ports to host interfaces
|
||||
- "80:80" # WebApp & EAS (alt. HTTP)
|
||||
- "143:143" # IMAP (not needed if all devices can use EAS)
|
||||
- "110:110" # POP3 (not needed if all devices can use EAS)
|
||||
- "8080:8080" # ICAL (not needed if all devices can use EAS)
|
||||
- "993:993" # IMAPS (not needed if all devices can use EAS)
|
||||
- "995:995" # POP3S (not needed if all devices can use EAS)
|
||||
- "8443:8443" # ICALS (not needed if all devices can use EAS)
|
||||
depends_on:
|
||||
- auth
|
||||
- mail-db
|
||||
- mail-mta
|
||||
environment:
|
||||
- db
|
||||
- mta
|
||||
environment: # Virgin config, ignored on restarts unless FORCE_CONFIG given.
|
||||
- USER_PLUGIN=ldap
|
||||
- LDAP_HOST=auth
|
||||
- MYSQL_HOST=mail-db
|
||||
- SMTP_SERVER=mail-mta
|
||||
- LDAP_SEARCH_BASE=${LDAP_BASE-dc=example,dc=com}
|
||||
- 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
|
||||
- LDAP_URI=ldap://auth:389/
|
||||
- MYSQL_HOST=db
|
||||
- SMTP_SERVER=mta
|
||||
- LDAP_SEARCH_BASE=${AD_BASE-dc=example,dc=com}
|
||||
- LDAP_USER_TYPE_ATTRIBUTE_VALUE=${AD_USR_OB-kopano-user}
|
||||
- LDAP_GROUP_TYPE_ATTRIBUTE_VALUE=${AD_GRP_OB-kopano-group}
|
||||
- LDAP_GROUPMEMBERS_ATTRIBUTE_TYPE=dn
|
||||
- LDAP_PROPMAP=
|
||||
- DAGENT_PLUGINS=movetopublicldap
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE-kopano}
|
||||
- MYSQL_USER=${MYSQL_USER-kopano}
|
||||
- MYSQL_PASSWORD=${MYSQL_PASSWORD-secret}
|
||||
- IMAP_LISTEN=*:143 # also listen to eth0
|
||||
- POP3_LISTEN=*:110 # also listen to eth0
|
||||
- ICAL_LISTEN=*:8080 # also listen to eth0
|
||||
- IMAPS_LISTEN=*:993 # enable TLS
|
||||
- POP3S_LISTEN=*:995 # enable TLS
|
||||
- ICALS_LISTEN=*:8443 # enable TLS
|
||||
- PLUGIN_SMIME_USER_DEFAULT_ENABLE_SMIME=true
|
||||
- SYSLOG_LEVEL=${SYSLOG_LEVEL-3}
|
||||
- LOG_LEVEL=${LOG_LEVEL-3}
|
||||
volumes:
|
||||
- mail-conf:/etc/kopano
|
||||
- mail-atch:/var/lib/kopano/attachments
|
||||
- mail-sync:/var/lib/z-push
|
||||
- app-conf:/etc/kopano
|
||||
- app-atch:/var/lib/kopano/attachments
|
||||
- app-sync:/var/lib/z-push
|
||||
- app-spam:/var/lib/kopano/spamd # kopano-spamd integration
|
||||
- /etc/localtime:/etc/localtime:ro # Use host timezone
|
||||
cap_add: # helps debugging by allowing strace
|
||||
- sys_ptrace
|
||||
|
||||
mail-mta:
|
||||
mta:
|
||||
image: mlan/postfix-amavis
|
||||
restart: unless-stopped
|
||||
hostname: ${MAIL_SRV-mx}.${MAIL_DOMAIN-docker.localhost}
|
||||
hostname: ${MAIL_SRV-mx}.${MAIL_DOMAIN-example.com}
|
||||
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
|
||||
ports: # Expose ports to host interfaces
|
||||
- "25:25" # SMTP
|
||||
- "465:465" # SMTPS authentication required
|
||||
depends_on:
|
||||
- auth
|
||||
environment:
|
||||
- MESSAGE_SIZE_LIMIT=${MESSAGE_SIZE_LIMIT-25600000}
|
||||
environment: # Virgin config, ignored on restarts unless FORCE_CONFIG given.
|
||||
- LDAP_HOST=auth
|
||||
- VIRTUAL_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
|
||||
- VIRTUAL_TRANSPORT=lmtp:app:2003
|
||||
- LDAP_USER_BASE=ou=${AD_USR_OU-users},${AD_BASE-dc=example,dc=com}
|
||||
- LDAP_QUERY_FILTER_USER=(&(objectclass=${AD_USR_OB-kopano-user})(mail=%s))
|
||||
volumes:
|
||||
- mail-mta:/var
|
||||
- proxy-acme:/acme
|
||||
- mta:/srv
|
||||
- app-spam:/var/lib/kopano/spamd # kopano-spamd integration
|
||||
- /etc/localtime:/etc/localtime:ro # Use host timezone
|
||||
cap_add: # helps debugging by allowing strace
|
||||
- sys_ptrace
|
||||
|
||||
mail-db:
|
||||
db:
|
||||
image: mariadb
|
||||
restart: unless-stopped
|
||||
command: ['--log_warnings=1']
|
||||
networks:
|
||||
- backend
|
||||
environment:
|
||||
- LANG=C.UTF-8
|
||||
env_file:
|
||||
- .init.env
|
||||
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD-secret}
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE-kopano}
|
||||
- MYSQL_USER=${MYSQL_USER-kopano}
|
||||
- MYSQL_PASSWORD=${MYSQL_PASSWORD-secret}
|
||||
volumes:
|
||||
- mail-db:/var/lib/mysql
|
||||
- db:/var/lib/mysql
|
||||
- /etc/localtime:/etc/localtime:ro # Use host timezone
|
||||
|
||||
auth:
|
||||
image: mlan/openldap:1
|
||||
restart: unless-stopped
|
||||
image: mlan/openldap
|
||||
networks:
|
||||
- backend
|
||||
command: --root-cn ${AD_ROOT_CN-admin} --root-pw ${AD_ROOT_PW-secret}
|
||||
environment:
|
||||
- LDAP_LOGLEVEL=parse
|
||||
- LDAPBASE=${AD_BASE-dc=example,dc=com}
|
||||
- LDAPDEBUG=${AD_DEBUG-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}
|
||||
- traefik.frontend.auth.basic=${PROXY_USER-admin}:${PROXY_PASSWORD-secret}
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- proxy-acme:/acme
|
||||
- /dev/null:/traefik.toml
|
||||
- auth:/srv
|
||||
- /etc/localtime:/etc/localtime:ro # Use host timezone
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
backend:
|
||||
|
||||
volumes:
|
||||
mail-conf:
|
||||
mail-atch:
|
||||
mail-db:
|
||||
mail-mta:
|
||||
mail-sync:
|
||||
proxy-acme:
|
||||
|
||||
app-atch:
|
||||
app-conf:
|
||||
app-spam:
|
||||
app-sync:
|
||||
auth:
|
||||
db:
|
||||
mta:
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
## Demo
|
||||
|
||||
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:
|
||||
This repository contains a [demo](demo) directory which hold the [docker-compose.yml](demo/docker-compose.yml) file as well as a [Makefile](demo/Makefile) which might come handy. To run the demo you need [docker-compose](https://docs.docker.com/compose/install/) installed. By default `curl` and `firefox` is expected to be installed, but if not, run `make utils-container` within the [demo](demo) directory once the repository has been cloned. The `make` utility works nicely with [bash-completion](https://github.com/scop/bash-completion) so it can be worth considering having it installed too. Once the dependencies are met, start with cloning the [github](https://github.com/mlan/docker-kopano) repository.
|
||||
|
||||
```bash
|
||||
docker exec mail-app man kopano-server.cfg
|
||||
git clone https://github.com/mlan/docker-kopano.git
|
||||
```
|
||||
|
||||
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.
|
||||
From within the [demo](demo) directory you can start the containers by typing:
|
||||
|
||||
```bash
|
||||
make init
|
||||
```
|
||||
|
||||
Now you can assess WebApp on the custom docker network at URL `http://app` and log in with the user name `demo` and password `demo`.
|
||||
|
||||
```bash
|
||||
make web
|
||||
```
|
||||
|
||||
You can send yourself a test email by typing:
|
||||
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
||||
When you are done testing you can destroy the test containers and their volumes by typing:
|
||||
|
||||
```bash
|
||||
make destroy
|
||||
```
|
||||
|
||||
## Persistent storage
|
||||
|
||||
By default, docker will store the user data and service configurations within the container. This has the drawback that the user data and service configurations are lost together with the container should it be deleted. It can therefore be a good idea to use docker volumes and mount the run directories and/or the configuration directories there so that the data will survive a container deletion.
|
||||
|
||||
There are at least three directories which should be considered for persistent storage; 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`.
|
||||
|
||||
## Configuration / seeding procedure
|
||||
|
||||
The `mlan/kopano` image contains an elaborate configuration / seeding procedure. The configuration is controlled by environment variables, described below.
|
||||
|
||||
The seeding procedure will leave any existing configuration untouched. This is achieved by the using an unlock file: `DOCKER_UNLOCK_FILE=/etc/kopano/.docker.unlock`.
|
||||
During the image build this file is created. When the the container is started the configuration / seeding procedure will be executed if the `DOCKER_UNLOCK_FILE` can be found. Once the procedure completes the unlock file is deleted preventing the configuration / seeding procedure to run when the container is restarted.
|
||||
|
||||
The unlock file approach was selected since it is difficult to accidentally _create_ a file.
|
||||
|
||||
In the rare event that want to modify the configuration of an existing container you can override the default behavior by setting `FORCE_CONFIG=overwrite` to a no-empty string.
|
||||
|
||||
## Environment variables and service parameters
|
||||
|
||||
When you create the `mlan/kopano` container, you can adjust the configuration of the Kopano server by defining one or more environment variables. During container initiation environment variables are matched against all possible parameters for all Kopano services. When matched, configuration files are updated with the value of the matching environment variable.
|
||||
|
||||
To see all available configuration parameters you can run `run list_parms <service>` within the container or when when using the [demo](#demo) described above just type:
|
||||
|
||||
```bash
|
||||
make app-parms_dagent
|
||||
make app-parms_server
|
||||
```
|
||||
|
||||
### Overlapping parameter names
|
||||
|
||||
Some services use the same parameter names. When such a parameter is set using en environment variable all configuration files of the related services will be updated. This is not always desired. To address this you can prefix the parameter name with the name of the service you which to target by using the following syntax: `<service>_<parameter>`.
|
||||
|
||||
For example when using the [Kopano-archiver](https://documentation.kopano.io/kopano_archiver_manual/) service, you often want to use one SQL database for the server and one separate one for the archiver. In this situation you can have two separate SQL database containers, one at `db-srv` and the other at `db-arc`. To set the [`MYSQL_HOST`](#mysql_host) parameter in the two relevant configuration files use `SERVER_MYSQL_HOST=db-srv` and `ARCHIVER_MYSQL_HOST=db-arc`. If for some reason you want both services to call the same container, instead use: `MYSQL_HOST=db`.
|
||||
|
||||
## 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.
|
||||
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`.
|
||||
The host name of the MySQL server to use. Default `MYSQL_HOST=localhost`.
|
||||
|
||||
#### `MYSQL_PORT`
|
||||
|
||||
|
@ -235,35 +269,31 @@ The location where attachments are stored. This can be in the MySQL database, or
|
|||
|
||||
#### `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`
|
||||
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
|
||||
## User management `USER_PLUGIN`
|
||||
|
||||
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`.
|
||||
Kopano supports three different plugins for user management. Use the `USER_PLUGIN` environment variable to select the source of the user base. Possible values are: `db` (default), `ldap` and `unix`.
|
||||
|
||||
## User authentication `USER_PLUGIN`
|
||||
`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 plug-in.
|
||||
|
||||
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
|
||||
`ldap`: Retrieve the users and groups information from an LDAP directory server. 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
|
||||
### Accessing an LDAP directory server
|
||||
|
||||
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).
|
||||
The `USER_PLUGIN=ldap` retrieves user information from an LDAP directory server. A brief description of how that is achieved is described in [Setup an LDAP directory server](#setup-an-ldap-directory-server). Once the LDAP directory server is up and running, the `mlan/kopano` container can be configured to use it using environment variables.
|
||||
|
||||
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`.
|
||||
#### Host address `LDAP_URI`
|
||||
|
||||
#### `LDAP_HOST`, `LDAP_PORT`, `LDAP_PROTOCOL`
|
||||
Specifies the URI of one or more LDAP server(s) to use, without any DN portion, such as `ldap://server:389/`, `ldaps://server:636/` or `ldapi:///`. Defaults: `LDAP_URI=ldap://localhost:389/`.
|
||||
|
||||
These directives specify a single LDAP server to use. Defaults: `LDAP_HOST=localhost`, `LDAP_PORT=389`, `LDAP_PROTOCOL=ldap`
|
||||
Note that. the historic directives `LDAP_HOST`, `LDAP_PORT`, `LDAP_PROTOCOL` are no longer supported (8.7.85).
|
||||
|
||||
#### `LDAP_SEARCH_BASE`
|
||||
|
||||
|
@ -281,40 +311,351 @@ This variable determines what defines a valid Kopano group. Default: `LDAP_GROUP
|
|||
|
||||
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.
|
||||
Hint: Use the `kopanoAccount` attribute in the filter to differentiate between non-Kopano and Kopano users.
|
||||
|
||||
### Enabling IMAP and POP3 `DISABLED_FEATURES`
|
||||
#### `LDAP_BIND_USER`, `LDAP_BIND_PASSWD`
|
||||
|
||||
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`
|
||||
The defaults for these environment variables are empty. If you cannot bind anonymously, do it with this distinguished name and password. Example: LDAP_BIND_USER=cn=admin,dc=example,dc=com, LDAP_BIND_PASSWD=secret.
|
||||
|
||||
### Logging `LOG_LEVEL`
|
||||
### Kopano LDAP attributes `LDAP_PROPMAP`
|
||||
|
||||
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`
|
||||
The Kopano services needs to know which of the users LDAP attributes, like addresses, phone numbers and company information, to use. This information is defined in the `propmap` file, which is included in the Kopano installation files here `/usr/share/kopano/ldap.propmap.cfg`. When using `USER_PLUGIN=ldap` this LDAP `propmap` file is used by the Kopano services by setting `LDAP_PROPMAP=` to an empty string. Optionally you can use another file, for example`LDAP_PROPMAP=/etc/kopano/ldap.propmap.cfg`. If no file can be found there the installed one will be copied there.
|
||||
|
||||
## Custom themes
|
||||
## Enabling IMAP, POP3 and ICAL
|
||||
|
||||
By default the [IMAP](https://www.atmail.com/blog/imap-commands/) and POP3 services are disabled for all users. Set the environment variable `DISABLED_FEATURES=` to an empty string to enable both IMAP and POP3 for all users. You can override this setting for each user independently by enabling or disabling features in the LDAP directory server see, [Setup an LDAP directory server](#setup-an-ldap-directory-server).
|
||||
|
||||
#### `DISABLED_FEATURES`
|
||||
|
||||
The environment variable `DISABLED_FEATURES` take a space separated list of features. Currently it may contain the following features: `imap`, `mobile`, `outlook`, `pop3` and `webapp`. Default: `DISABLED_FEATURES="imap pop3"`
|
||||
|
||||
#### `IMAP_LISTEN`, `POP3_LISTEN`and `ICAL_LISTEN`
|
||||
|
||||
By default the kopano-gateway and kopano-ical services are configured to only listen on the loop-back interface. To be able to access these services we need them to listen to any interface. This is achieved by setting `IMAP_LISTEN=*:143`, `POP3_LISTEN=*:110` and `ICAL_LISTEN=*:8080`. These port numbers can be changed if desired.
|
||||
|
||||
## Enabling IMAPS, POP3S and ICALS
|
||||
|
||||
By default the secure protocols are not enabled.
|
||||
|
||||
#### `IMAPS_LISTEN`, `POP3S_LISTEN`and `ICALS_LISTEN`
|
||||
|
||||
To enable secure access we need to explicitly define their listening ports. This is achieved by setting any combination of `IMAPS_LISTEN=*:993`, `POP3S_LISTEN=*:995` and `ICALS_LISTEN=*:8443`. These port numbers can be changed if desired.
|
||||
|
||||
If any of `IMAPS_LISTEN`, `POP3S_LISTEN` and `ICALS_LISTEN` are explicitly defined but there are no certificate files defined, a self-signed certificate will be generated when the container is created.
|
||||
|
||||
### SSL/LTS certificate and private key
|
||||
|
||||
For most deployments a trusted SSL/TLS certificate is desired. During startup the `mlan/kopano` looks for [RSA](https://en.wikipedia.org/wiki/RSA_(cryptosystem)) [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) certificate and private key with these specific names: `/etc/kopano/ssl/cert.pem` and `/etc/kopano/ssl/priv_key.pem`. If found they will used by the secure protocols IMAPS, POP3S and ICALS. Moreover the file ownership will be changed if needed to make them readable by the Kopano services.
|
||||
|
||||
#### `SSL_CERTIFICATE_FILE` and `SSL_PRIVATE_KEY_FILE`
|
||||
|
||||
If you use other file names or directories, you let the Kopano services know by setting the variables `SSL_CERTIFICATE_FILE=/etc/kopano/ssl/cert.pem` and `SSL_PRIVATE_KEY_FILE=/etc/kopano/ssl/priv_key.pem` on the `docker run` command line or in the `docker-compose.yml` file.
|
||||
|
||||
For testing purposes you can create a self-signed certificate using the `openssl` utility, see below. Note that this is not necessary since, when secure protocols are defined, a self-signed certificate and private key will be automatically be created during container startup if they are not found.
|
||||
|
||||
```bash
|
||||
openssl genrsa -out ssl/priv_key.pem
|
||||
openssl req -x509 -utf8 -new -batch -subj "/CN=app" -key ssl/priv_key.pem -out ssl/cert.pem
|
||||
```
|
||||
|
||||
One way to allow the container to read the certificate and private key is to bind mount the host directory holding the files to the container:
|
||||
|
||||
```bash
|
||||
docker run -d -name app -v $pwd/ssl:/etc/kopano/ssl mlan/kopano
|
||||
```
|
||||
|
||||
A other way is to copy them to the container:
|
||||
|
||||
```bash
|
||||
docker create -name app mlan/kopano
|
||||
docker cp ssl/. app:/etc/kopano/ssl
|
||||
docker start app
|
||||
```
|
||||
|
||||
If you copy the files to a running container you need to make sure that the user `kopano` can read them.
|
||||
|
||||
### Let’s Encrypt LTS certificates using [Traefik](https://docs.traefik.io/)
|
||||
|
||||
[Let’s Encrypt](https://letsencrypt.org/) provide free, automated, authorized certificates when you can demonstrate control over your domain. [Automatic Certificate Management Environment (ACME)](https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment) is the protocol used for such demonstration. There are many agents and applications that supports ACME, e.g., [certbot](https://certbot.eff.org/). The reverse proxy [Traefik](https://docs.traefik.io/) also supports ACME.
|
||||
|
||||
#### `ACME_FILE`, `ACME_POSTHOOK`
|
||||
|
||||
The `mlan/kopano` image looks for a file `ACME_FILE=/acme/acme.json` at container startup and every time this file changes certificates within this file are extracted. If the host or domain name of one of those certificates matches `HOSTNAME=$(hostname)` or `DOMAIN=${HOSTNAME#*.}` it will be used by the secure protocols.
|
||||
|
||||
Once the certificates and keys have been updated, we run the command in the environment variable `ACME_POSTHOOK="sv restart kopano-gateway kopano-ical"`. Kopano services needs to be restarted to update the LTS parameters. If such automatic reloading is not desired, set `ACME_POSTHOOK=` to empty.
|
||||
|
||||
So reusing certificates from Traefik will work out of the box if the `/acme` directory in the Traefik container is also mounted in the `mlan/kopano` container.
|
||||
|
||||
```bash
|
||||
docker run -d -name proxy -v proxy-acme:/acme traefik
|
||||
docker run -d -name app -v proxy-acme:/acme:ro mlan/kopano
|
||||
```
|
||||
|
||||
Note, if the target certificate Common Name (CN) or Subject Alternate Name (SAN) is changed the container needs to be restarted.
|
||||
|
||||
Moreover, do not set any of `SSL_CERTIFICATE_FILE` and `SSL_PRIVATE_KEY_FILE` when using `ACME_FILE`.
|
||||
|
||||
## Logging `SYSLOG_LEVEL`, `LOG_LEVEL`
|
||||
|
||||
The level of output for logging is in the range from 0 to 7. The default is: `SYSLOG_LEVEL=5`.
|
||||
|
||||
| emerg | alert | crit | err | warning | notice | info | debug |
|
||||
| ----- | ----- | ---- | ---- | ------- | ------ | ---- | ----- |
|
||||
| 0 | 1 | 2 | 3 | 4 | **5** | 6 | 7 |
|
||||
|
||||
Separately, `LOG_LEVEL` controls the logging level of the Kopano services. `LOG_LEVEL` takes valued from 0 to 6, where the default is `LOG_LEVEL=3`.
|
||||
|
||||
| none | crit | err | warning | notice | info | debug |
|
||||
| ---- | ---- | ---- | ------- | ------ | ---- | ----- |
|
||||
| 0 | 1 | 2 | **3** | 4 | 5 | 6 |
|
||||
|
||||
## Kopano add-ons
|
||||
|
||||
### Kopano Archiver
|
||||
|
||||
The [Kopano Archiver](https://documentation.kopano.io/kopano_archiver_manual/) provides a Hierarchical Storage Management (HSM) solution for Kopano. With the Kopano Archiver older messages will be automatically moved to slower and thus cheaper storage. The slow storage consists of one or more additional Kopano Archive servers which sole task it is to store archived messages.
|
||||
|
||||
Typically the archiver needs its own SQL database. You can configure it using environment variables. When you do, pay attention to [overlapping parameter names](#overlapping-parameter-names). Also the archiver does not run as a daemon but instead you can set up [cron](#cron) jobs. For example, to run the archiver daily with weakly clean-up you can use; `CRONTAB_ENTRY1=0 1 * * * root kopano-archiver -A` and `CRONTAB_ENTRY2=0 3 * * 0 root kopano-archiver -C`.
|
||||
|
||||
## WebApp 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);'
|
||||
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 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.
|
||||
|
||||
### Mail transfer agent interaction
|
||||
## WebApp plugins
|
||||
|
||||
### S/MIME
|
||||
|
||||
[S/MIME](https://en.wikipedia.org/wiki/S/MIME) provides [email encryption](https://en.wikipedia.org/wiki/Email_encryption) guaranteeing the confidentiality and non-repudiation of email. The [S/MIME](https://documentation.kopano.io/webapp_smime_manual/) WebApp plugin is pre-installed.
|
||||
|
||||
Using the [demo](#demo) you can easily create a S/MIME certificate you can try out using WebApp.
|
||||
|
||||
```sh
|
||||
make app-create_smime
|
||||
```
|
||||
|
||||
### Mobile device management
|
||||
|
||||
The [Mobile Device Management](https://documentation.kopano.io/webapp_mdm_manual/) WebApp plugin comes pre-installed. With it you can resync, remove, refresh and even wipe your devices, connected via [Exchange ActiveSync (EAS)](https://en.wikipedia.org/wiki/Exchange_ActiveSync).
|
||||
|
||||
## Public folders
|
||||
|
||||
There are two type of stores (folders containing communication elements); private and public stores. There can only be one public store. It is the Kopano dagent that places incoming messages into mail boxes, that is the private and public stores.
|
||||
|
||||
Public folders are configured by the system admin. Users have them mapped automatically. The shared and public folders can be synced with mobile devices via [Exchange ActiveSync (EAS)](https://wiki.z-hub.io/display/ZP/Sharing+folders+and+Read-only). Each user can manage which shared or public folders to sync with her mobile devices by using the [Mobile Device Management](#mobile-device-management) WebApp plugin.
|
||||
|
||||
With the current Kopano implementation, delivering to the public store is configured separately from normal user management. There is the [move to public](https://documentation.kopano.io/kopanocore_administrator_manual/special_kc_configurations.html#move-to-public) plugin which moves incoming messages to a folder in the public store. It has a static configuration and does not support LDAP lookup.
|
||||
|
||||
### Move to public with LDAP lookup
|
||||
|
||||
The `mlan/kopano` image include a extended version of the move to public plugin which use LDAP lookup, instead of a static file based lookup. When the plugin [move to public with LDAP](src/kopano/plugin/movetopublicldap.py) is enabled, `DAGENT_PLUGINS=movetopublicldap`, the kopano dagent will do two LDAP queries. The first is to search for an entry/user with matching email address. The second, introduced with this plugin, is to get the public folder from this entry. If found the message will be delivered to the public folder otherwise it will be delivered to the mailbox of the user.
|
||||
|
||||
Lets demonstrate how delivery to a public folder is configured. With this LDAP entry:
|
||||
|
||||
```yaml
|
||||
dn: uid=public,ou=users,dc=example,dc=com
|
||||
cn: public
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: kopano-user
|
||||
sn: public
|
||||
uid: public
|
||||
mail: public@example.com
|
||||
kopanoAccount: 1
|
||||
kopanoHidden: 1
|
||||
kopanoSharedStoreOnly: 1
|
||||
kopanoResourceType: publicFolder:Public Stores/public
|
||||
```
|
||||
|
||||
messages to `public@example.com` will be delivered to the public store in `Public Stores/public`.
|
||||
The central [attribute](https://documentation.kopano.io/kopanocore_administrator_manual/appendix_b.html#appendix-b-ldap-attribute-description) is `kopanoResourceType: publicFolder:Public Stores/public`. It contains a token and a folder name. The token match is case sensitive and there must be a colon `:` separating
|
||||
the token and the public folder name. The folder name can contain space and
|
||||
sub folders, which are distinguished using a forward slash `/`.
|
||||
|
||||
The parameters in `/etc/kopano/ldap.cfg` will be used to arrange the LDAP queries.
|
||||
The LDAP attribute holding the token and the token itself have the following
|
||||
default values, which can be modified in `/etc/kopano/movetopublicldap.cfg` if desired.
|
||||
|
||||
```yaml
|
||||
ldap_public_folder_attribute = kopanoResourceType
|
||||
ldap_public_folder_attribute_token = publicFolder
|
||||
```
|
||||
|
||||
As with other parameters, environment variables can be used to define them: `LDAP_PUBLIC_FOLDER_ATTRIBUTE=kopanoResourceType` and `LDAP_PUBLIC_FOLDER_ATTRIBUTE_TOKEN=publicFolder`.
|
||||
|
||||
## Shared folders
|
||||
|
||||
Users can share folders when sufficient permission have been granted. When logged into WebApp with an administrative account (`kopanoAdmin: 1`) you can modify the permissions on users shares and folders. Users can then, when logged into WebApp, open the inbox of other users by selecting `Open Shared Mails`.
|
||||
|
||||
The [impersonation](https://wiki.z-hub.io/display/ZP/Impersonation) mechanism allow such shared folders to be synced over [Exchange ActiveSync (EAS)](https://wiki.z-hub.io/display/ZP/Sharing+folders+and+Read-only) too. Each user can manage which shared or public folders to sync with her mobile devices by using the [Mobile Device Management](#mobile-device-management) WebApp plugin.
|
||||
|
||||
## Crontab
|
||||
|
||||
The `mlan/kopano` has a [cron](https://en.wikipedia.org/wiki/Cron) service activated. You can use environment variables to set up [crontab](https://man7.org/linux/man-pages/man5/crontab.5.html) entries. Any environment variable name staring with `CRONTAB_ENTRY` will be use to add entries to cron.
|
||||
|
||||
One trivial example is `CRONTAB_ENTRY_TEST=* * * * * root logger -t cron -p user.notice "SHELL=$$SHELL, PATH=$$PATH"`.
|
||||
|
||||
During the initial configuration procedure any `CRONTAB_ENTRY` will add crontab entries to the file `/etc/kopano/docker-crontab`, all the while previously present entries are deleted. This file defines the `PATH` variable so that you don't need to give full path names to commands in the crontab entry. This is, you need to provide the full path names to commands if this `PATH` definition is missing in the `/etc/kopano/docker-crontab` file.
|
||||
|
||||
## 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.
|
||||
|
||||
#### `LMTP_LISTEN`
|
||||
|
||||
Added support (release 1.1.3) of the environment variable with default `LMTP_LISTEN=*:2003`, due to misconfiguration of `kopano-dagent` in recent releases (kopano-core 8.7.84).
|
||||
|
||||
#### `SMTP_SERVER`
|
||||
|
||||
Hostname or IP address of the outgoing SMTP server. This server needs to relay mail for your server. Default: `SMTP_SERVER=localhost`
|
||||
Host name 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`
|
||||
TCP Port number used to contact the `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 `VIRTUAL_TRANSPORT=lmtp:mail-app:2003` assuming the `mlan/kopano` container is named `mail-app`
|
||||
The Kopano server listens to the port 2003 and expect the [LMTP](https://en.wikipedia.org/wiki/Local_Mail_Transfer_Protocol) protocol. For Postfix you can define `VIRTUAL_TRANSPORT=lmtp:mail-app:2003` assuming the `mlan/kopano` container is named `mail-app`
|
||||
|
||||
## Kopano-spamd integration with [mlan/postfix-amavis](https://github.com/mlan/docker-postfix-amavis)
|
||||
|
||||
[Kopano-spamd](https://kb.kopano.io/display/WIKI/Kopano-spamd) allow users to
|
||||
drag messages into the Junk folder triggering the anti-spam filter to learn it as spam. If the user moves the message back to the inbox,
|
||||
the anti-spam filter will unlearn it.
|
||||
|
||||
To allow kopano-spamd integration the kopano and postfix-amavis containers need to
|
||||
share the `/var/lib/kopano/spamd` folder. If this directory exists within the
|
||||
postfix-amavis container, the spamd-spam and spamd-ham service will be started.
|
||||
They will run `sa-learn --spam` or `sa-learn --ham`,
|
||||
respectively when a message is placed in either `var/lib/kopano/spamd/spam` or
|
||||
`var/lib/kopano/spamd/ham`.
|
||||
|
||||
## Migrate old configuration to newer version of Kopano
|
||||
|
||||
Sometimes a new version of Kopano breaks compatibility with old configurations. The `mlan/kopano` include some functionality to address such situations. Use`MIGRATE_CONFIG` to try to attempt all or a list of available fixes. `MIGRATE_CONFIG=1 2 3` is an example of a list of fixes and `MIGRATE_CONFIG=all` attempts all fixes.
|
||||
|
||||
### `MIGRATE_CONFIG=1` Rejected insecure request as configuration for SECURE_COOKIES is true
|
||||
|
||||
Prior to Kopano WebApp version 5.0.0 the parameter was `define("INSECURE_COOKIES", true);` was used to allow HTTP access. Now [`define("SECURE_COOKIES", false);`](https://documentation.kopano.io/webapp_admin_manual/config.html#secure-cookies) is used instead. This fix tries to update the configuration accordingly.
|
||||
|
||||
### `MIGRATE_CONFIG=2` Make sure WebApp plugins have configuration files in place
|
||||
|
||||
The WebApp plugins S/MIME and MDM has recently been added to the `mlan/kopano` image. Old deployments might not have the related configuration files in place, preventing these plugins from running. This fix places default copies of configuration files in the configuration directory should they be missing.
|
||||
|
||||
# Knowledge base
|
||||
|
||||
Here some topics relevant for arranging a mail server are presented.
|
||||
|
||||
## Setup an LDAP directory server
|
||||
|
||||
A [Directory Server Agent (DSA)](https://en.wikipedia.org/wiki/Directory_System_Agent) is used to store, organize and present data in a key-value type format. Typically, directories are optimized for lookups, searches, and read operations over write operations, so they function extremely well for data that is referenced often but changes infrequently.
|
||||
|
||||
The [Lightweight Directory Access Protocol (LDAP)](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol), is an open protocol used to store and retrieve data from a hierarchical directory structure. Commonly used to store information about an organization and its assets and users.
|
||||
|
||||
[OpenLDAP](https://www.openldap.org/) is a cross platform LDAP based directory service. [Active Directory (AD)](https://en.wikipedia.org/wiki/Active_Directory) is a [directory service](https://en.wikipedia.org/wiki/Directory_service) developed by [Microsoft](https://en.wikipedia.org/wiki/Microsoft) for [Windows domain](https://en.wikipedia.org/wiki/Windows_domain) networks which also uses LDAP.
|
||||
|
||||
There are many dockerized OpenLDAP server to choose from. One such example is [mlan/openldap](https://github.com/mlan/docker-openldap).
|
||||
|
||||
### LDAP user entry
|
||||
|
||||
The data itself in an LDAP system is mainly stored in elements called attributes. Attributes are basically key-value pairs. Unlike in some other systems, the keys have predefined names which are dictated by the objectClasses selected for an entry. An entry is basically a collection of attributes under a name used to describe something; a user for example.
|
||||
|
||||
```yaml
|
||||
dn: uid=demo,ou=users,dc=example,dc=com
|
||||
objectclass: inetOrgPerson
|
||||
uid: demo
|
||||
```
|
||||
|
||||
### Kopano LDAP schema
|
||||
|
||||
The Kopano LDAP schema defines additional objectClasses and attributes. These allow the Kopano services, access for example, to controlled on per-user basis. More information on available attributes can be find here, [Fine-tuning user configuration](https://documentation.kopano.io/kopanocore_administrator_manual/configure_kc_components.html?highlight=propmap#fine-tuning-user-configuration).
|
||||
|
||||
```shell
|
||||
dn: uid=demo,ou=users,dc=example,dc=com
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: kopano-user
|
||||
objectClass: posixAccount
|
||||
cn: demo
|
||||
sn: demo
|
||||
uid: demo
|
||||
mail: demo@example.com
|
||||
uidNumber: 1234
|
||||
gidNumber: 1234
|
||||
homeDirectory: /home/demo
|
||||
telephoneNumber: 0123 123456789
|
||||
title: MCP
|
||||
kopanoEnabledFeatures: imap
|
||||
kopanoEnabledFeatures: pop3
|
||||
```
|
||||
|
||||
The schema needs to be added to the directory server. The Kopano installation files include the LDAP schema and can be found here `/usr/share/doc/kopano/kopano.ldif.gz`. For more details, 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).
|
||||
|
||||
## Kopano WebApp HTTP access
|
||||
|
||||
The distribution installation of `kopano-webapp` only allow HTTPS access. The `mlan/kopano` image updates the configuration to [`define("SECURE_COOKIES", false);`](https://documentation.kopano.io/webapp_admin_manual/config.html#secure-cookies) in `/etc/kopano/webapp/config.php` also allowing HTTP access. This can be useful when arranging the `mlan/kopano` container behind a reverse proxy, like [Traefik](https://doc.traefik.io/traefik/), which then does the enforcement of HTTPS. Also see [`MIGRATE_CONFIG=1` Rejected insecure request as configuration for SECURE_COOKIES is true](#migrate_config1-rejected-insecure-request-as-configuration-for-secure_cookies-is-true).
|
||||
|
||||
## Mail client configuration
|
||||
|
||||
### Microsoft Outlook
|
||||
|
||||
Kopano, using [Z-Push](http://z-push.org/), allows native interfacing with Microsoft Outlook 2013 and above via the [Exchange ActiveSync (EAS)](https://en.wikipedia.org/wiki/Exchange_ActiveSync) protocol, providing synchronization of mail, calendar, tasks and contacts. For details please see [Configuring Outlook](https://documentation.kopano.io/user_manual_kopanocore/configure_outlook.html).
|
||||
|
||||
It can be interesting to know that there is a [Kopano OL Extension](https://kb.kopano.io/display/WIKI/Setting+up+the+Kopano+OL+Extension) that can improve productivity. To install it [download](https://download.kopano.io/community/olextension%3A/) and run the `KopanoOLExtension-<version>-combined.exe` file on your Windows PC.
|
||||
|
||||
### Mobile devices
|
||||
|
||||
Most mobile devices, that is, Apple iOS, Android and Blackberry have support for [Exchange ActiveSync (EAS)](https://en.wikipedia.org/wiki/Exchange_ActiveSync), providing synchronization of mail, calendar, tasks and contacts. For details please see [Configuring Mobile Devices](https://documentation.kopano.io/user_manual_kopanocore/configure_mobile_devices.html).
|
||||
|
||||
### Alternative mail synchronization
|
||||
|
||||
Some clients does not support [Exchange ActiveSync (EAS)](https://en.wikipedia.org/wiki/Exchange_ActiveSync), e.g., Linux ones, in which case either the [IMAP](https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol) or [POP3](https://en.wikipedia.org/wiki/Post_Office_Protocol) protocol are used via the Kopano gateway. These protocols only handle incoming mail, so for sending mail clients need to interface directly with a [Mail Transfer Agent (MTA)](https://en.wikipedia.org/wiki/Message_transfer_agent) over [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol). For more details please see [Configuring Alternative Mail Clients](https://documentation.kopano.io/user_manual_kopanocore/configure_alternative_mail_clients.html).
|
||||
|
||||
Please note that IMAP and POP3 access are not enabled by default, see [Enabling IMAP and POP3 `DISABLED_FEATURES`](#enabling-imap-and-pop3-disabled_features).
|
||||
|
||||
### Alternative calendar synchronization
|
||||
|
||||
CalDAV offers calendar sync For more details please see [Configuring CalDAV Clients](https://documentation.kopano.io/user_manual_kopanocore/configure_caldav_clients.html).
|
||||
|
||||
### Mozilla Thunderbird
|
||||
|
||||
Thunderbird does not support [Exchange ActiveSync (EAS)](https://en.wikipedia.org/wiki/Exchange_ActiveSync), so either [IMAP](https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol) or [POP3](https://en.wikipedia.org/wiki/Post_Office_Protocol) and SMTP is needed to synchronize mail, see [Alternative mail synchronization](#alternative-mail-synchronization).
|
||||
|
||||
To synchronize calendar, tasks and contacts CalDAV can be used. Interestingly Thunderbird has a add-on that provides calendar, tasks and contacts synchronization using [Exchange ActiveSync (EAS)](https://en.wikipedia.org/wiki/Exchange_ActiveSync), but not for mail. For details please see [Provider for Exchange ActiveSync](https://addons.thunderbird.net/en-US/thunderbird/addon/eas-4-tbsync/).
|
||||
|
||||
# Implementation
|
||||
|
||||
Here some implementation details are presented.
|
||||
|
||||
## Container init scheme
|
||||
|
||||
The container use [runit](http://smarden.org/runit/), providing an init scheme and service supervision, allowing multiple services to be started. There is a Gentoo Linux [runit wiki](https://wiki.gentoo.org/wiki/Runit).
|
||||
|
||||
When the container is started, execution is handed over to the script [`docker-entrypoint.sh`](src/docker/bin/docker-entrypoint.sh). It has 4 stages; 0) *register* the SIGTERM [signal (IPC)](https://en.wikipedia.org/wiki/Signal_(IPC)) handler, which is programmed to run all exit scripts in `/etc/docker/exit.d/` and terminate all services, 1) *run* all entry scripts in `/etc/docker/entry.d/`, 2) *start* services registered in `SVDIR=/etc/service/`, 3) *wait* forever, allowing the signal handler to catch the SIGTERM and run the exit scripts and terminate all services.
|
||||
|
||||
The entry scripts are responsible for tasks like, seeding configurations, register services and reading state files. These scripts are run before the services are started.
|
||||
|
||||
There is also exit script that take care of tasks like, writing state files. These scripts are run when docker sends the SIGTERM signal to the main process in the container. Both `docker stop` and `docker kill --signal=TERM` sends SIGTERM.
|
||||
|
||||
## Build assembly
|
||||
|
||||
The entry and exit scripts, discussed above, as well as other utility scrips are copied to the image during the build phase. The source file tree was designed to facilitate simple scanning, using wild-card matching, of source-module directories for files that should be copied to image. Directory names indicate its file types so they can be copied to the correct locations. The code snippet in the `Dockerfile` which achieves this is show below.
|
||||
|
||||
```dockerfile
|
||||
COPY src/*/bin $DOCKER_BIN_DIR/
|
||||
COPY src/*/entry.d $DOCKER_ENTRY_DIR/
|
||||
```
|
||||
|
||||
There is also a mechanism for excluding files from being copied to the image from some source-module directories. Source-module directories to be excluded are listed in the file [`.dockerignore`](https://docs.docker.com/engine/reference/builder/#dockerignore-file). Since we don't want files from the module `notused` we list it in the `.dockerignore` file:
|
||||
|
||||
```sh
|
||||
src/notused
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# Road map
|
||||
|
||||
## Revisit Persistent Data
|
||||
|
||||
Consider consolidating directories which are candidates for persistence under `/srv`.
|
||||
|
||||
- /etc/kopano
|
||||
- /var/lib/kopano
|
||||
- /var/lib/z-push
|
||||
|
||||
### Kopano Search
|
||||
|
||||
The kopano-search module keeps its database here, /var/lib/kopano/search.
|
||||
Consider to also consolidating it under /srv to simplify making it persistent?
|
||||
|
||||
## kDAV
|
||||
|
||||
Consider integrating support for kDAV which provides CalDAV and CardDAV.
|
||||
|
||||
## webapp-passwd
|
||||
|
||||
Integrate [webapp-passwd](https://github.com/silentsakky/zarafa-webapp-passwd)?
|
||||
|
||||
## kopano-spamd and kopano-search logs
|
||||
|
||||
In [KC-1858](https://github.com/Kopano-dev/kopano-core/commit/4a7f833e170167ebfa4f4c55835f8760ce7617f3) we find:
|
||||
|
||||
> The syslog log method does not work correctly and thus this change
|
||||
> disables it. Until it is fixed, Python services do not support
|
||||
> the syslog log_method. Additionally an environment variable is
|
||||
> added, which allow to lift this restriction for testing when it
|
||||
> it set.
|
||||
|
20
TODO.md
20
TODO.md
|
@ -1,20 +0,0 @@
|
|||
# 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
|
||||
```
|
147
assets/conf
147
assets/conf
|
@ -1,147 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# config
|
||||
#
|
||||
|
||||
#
|
||||
# usage
|
||||
#
|
||||
|
||||
usage() { echo "
|
||||
USAGE
|
||||
conf COMMAND FILE [command-options]
|
||||
|
||||
COMMAND
|
||||
modify <file> <parameter statement>
|
||||
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 <file> <old-string> <new-string>
|
||||
match <old-string> and relpace it with <new-string>
|
||||
Examples:
|
||||
conf replace /etc/amavisd.conf /var/run/clamav/clamd.sock /run/clamav/clamd.sock
|
||||
|
||||
uncommentsection <file> <string>
|
||||
Remove all leading '#' starting with a line that matches <string> and
|
||||
ending with an empty line
|
||||
Examples:
|
||||
conf uncommentsection /etc/amavisd.conf '# ### http://www.clamav.net/'
|
||||
|
||||
comment <file> <string>
|
||||
Add leading '#' to line matching <string>
|
||||
Examples:
|
||||
conf comment /etc/clamav/freshclam.conf UpdateLogFile
|
||||
|
||||
addafter <file> <start-string> <add-string> [<stop-string>]
|
||||
Add <add-string> after line matching <start-string> folowed by an empty line or a line matching <stop-string>
|
||||
Examples:
|
||||
conf addafter /etc/amavisd.conf '@local_domains_maps' '$inet_socket_bind = '\''127.0.0.1'\'';'
|
||||
|
||||
fixmissing <file> <source> [<source>]
|
||||
If <file> is missing create it using the first found <source>
|
||||
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 "$@"
|
|
@ -1,3 +0,0 @@
|
|||
#!/bin/bash
|
||||
pkg=${1-kopano-server}
|
||||
dpkg -l | sed -nr 's/.*'"$pkg"'\s+([^ ]+).*/\1/p'
|
|
@ -1,106 +0,0 @@
|
|||
#!/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
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#!/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
|
|
@ -1,98 +0,0 @@
|
|||
#!/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 "$@"
|
|
@ -0,0 +1,34 @@
|
|||
COMPOSE_PROJECT_NAME=demo
|
||||
SYSLOG_LEVEL=7
|
||||
LOG_LEVEL=6
|
||||
AD_DEBUG=stats
|
||||
AD_ADM_CN=admin
|
||||
AD_ADM_PW=admin
|
||||
AD_ADM_TEL=555-540-9637
|
||||
AD_ADM_TIT=System Admin
|
||||
AD_BASE=dc=example,dc=com
|
||||
AD_GRP_CN=team
|
||||
AD_GRP_OB=kopano-group
|
||||
AD_GRP_OU=groups
|
||||
AD_PUB_CN=public
|
||||
AD_ROOT_CN=admin
|
||||
AD_ROOT_PW=secret
|
||||
AD_SHR_CN=shared
|
||||
AD_USR_AS=trial
|
||||
AD_USR_CN=demo
|
||||
AD_USR_OB=kopano-user
|
||||
AD_USR_OU=users
|
||||
AD_USR_PW=demo
|
||||
AD_USR_TEL=555-439-2736
|
||||
AD_USR_TIT=First User
|
||||
DKIM_SELECTOR=default
|
||||
MAIL_DOMAIN=example.com
|
||||
MAIL_SRV=mx
|
||||
MYSQL_DATABASE=kopano
|
||||
MYSQL_PASSWORD=secret
|
||||
MYSQL_ROOT_PASSWORD=secret
|
||||
MYSQL_USER=kopano
|
||||
RAZOR_REGISTRATION=
|
||||
REGEX_ALIAS='/([^+]+)[+-].*@(.+)/ $1@$2'
|
||||
SA_DEBUG=0
|
||||
SA_TAG_LEVEL_DEFLT=-999
|
|
@ -0,0 +1,2 @@
|
|||
ssl
|
||||
utils-container.mk
|
|
@ -0,0 +1,438 @@
|
|||
# Makefile
|
||||
#
|
||||
# demo
|
||||
#
|
||||
|
||||
-include *.mk .env .init.env
|
||||
|
||||
SRV_LIST ?= auth app db mta
|
||||
|
||||
AD_DOM ?= $(call ad_sub_dot, $(AD_BASE))
|
||||
AD_DC ?= $(call ad_cut_dot, 1, 1, $(AD_DOM))
|
||||
|
||||
SSL_O = $(MAIL_DOMAIN)
|
||||
SSL_MAIL = auto
|
||||
SSL_PASS = $(AD_USR_PW)
|
||||
#SSL_TRST = $(SSL_SMIME)
|
||||
|
||||
NET_NAME ?= $(COMPOSE_PROJECT_NAME)_backend
|
||||
CURL_OPT ?= -s -v
|
||||
TSSL_CMD ?= docker run -i --rm --network $(NET_NAME) drwetter/testssl.sh
|
||||
CURL_CMD ?= curl
|
||||
webb_cmd ?= firefox $(1) &
|
||||
APP_NAME = app
|
||||
AUT_NAME = auth
|
||||
AUW_NAME = auth-web
|
||||
DB_NAME = db
|
||||
DBW_NAME = db-web
|
||||
MTA_NAME = mta
|
||||
APP_FQDN ?= $(call dkr_srv_ip,$(APP_NAME))
|
||||
AUT_FQDN ?= $(call dkr_srv_ip,$(AUT_NAME))
|
||||
AUW_FQDN ?= $(call dkr_cnt_ip,$(AUW_NAME))
|
||||
DB_FQDN ?= $(call dkr_srv_ip,$(DB_NAME))
|
||||
DBW_FQDN ?= $(call dkr_cnt_ip,$(DBW_NAME))
|
||||
MTA_FQDN ?= $(call dkr_srv_ip,$(MTA_NAME))
|
||||
|
||||
MAIL_FROM ?= test@my-domain.biz
|
||||
|
||||
variables:
|
||||
make -pn | grep -A1 "^# makefile"| grep -v "^#\|^--" | sort | uniq
|
||||
|
||||
test: all-test_quiet mta-test_smtp
|
||||
|
||||
init: up auth-init db-init app-restart mta-init app-init
|
||||
|
||||
ps:
|
||||
docker-compose ps
|
||||
|
||||
up:
|
||||
docker-compose up -d
|
||||
|
||||
down:
|
||||
docker-compose down
|
||||
|
||||
destroy: auth-web-down db-web-down all-destroy_smime
|
||||
docker-compose down -v
|
||||
|
||||
config:
|
||||
docker-compose config
|
||||
|
||||
logs:
|
||||
docker-compose logs --tail 10
|
||||
|
||||
images:
|
||||
docker-compose images
|
||||
|
||||
$(addsuffix -up,$(SRV_LIST)):
|
||||
docker-compose up -d $(patsubst %-up,%,$@)
|
||||
|
||||
$(addsuffix -down,$(SRV_LIST)):
|
||||
docker-compose rm -sf $(patsubst %-down,%,$@)
|
||||
|
||||
$(addsuffix -restart,$(SRV_LIST)):
|
||||
docker-compose restart $(patsubst %-restart,%,$@)
|
||||
|
||||
$(addsuffix -renew,$(SRV_LIST)):
|
||||
docker-compose rm -s $(patsubst %-renew,%,$@)
|
||||
docker-compose up -d $(patsubst %-renew,%,$@)
|
||||
|
||||
$(addsuffix -top,$(SRV_LIST)):
|
||||
docker-compose top $(patsubst %-top,%,$@)
|
||||
|
||||
$(addsuffix -logs,$(SRV_LIST)):
|
||||
docker-compose logs $(patsubst %-logs,%,$@)
|
||||
|
||||
$(addsuffix -pull,$(SRV_LIST)):
|
||||
docker-compose pull $(patsubst %-pull,%,$@)
|
||||
|
||||
$(addsuffix -sh,$(SRV_LIST)):
|
||||
docker-compose exec $(patsubst %-sh,%,$@) sh -c 'exec $$(getent passwd root | sed "s/.*://g")'
|
||||
|
||||
$(addsuffix -env,$(SRV_LIST)):
|
||||
docker-compose exec $(patsubst %-env,%,$@) env
|
||||
|
||||
$(addsuffix -sv,$(SRV_LIST)):
|
||||
docker-compose exec $(patsubst %-sv,%,$@) sh -c 'sv status $$SVDIR/*'
|
||||
|
||||
$(addsuffix -diff,$(SRV_LIST)):
|
||||
docker container diff $(COMPOSE_PROJECT_NAME)_$(patsubst %-diff,%,$@)_1
|
||||
|
||||
wait_%:
|
||||
sleep $*
|
||||
|
||||
web: app-web
|
||||
|
||||
auth-init: wait_3 auth-mod_conf auth-add_schema auth-add_data
|
||||
|
||||
export define LDIF_MOD_CONF
|
||||
dn: olcDatabase={-1}frontend,cn=config
|
||||
changetype: modify
|
||||
add: olcPasswordHash
|
||||
olcPasswordHash: {CRYPT}
|
||||
|
||||
dn: cn=config
|
||||
changetype: modify
|
||||
add: olcPasswordCryptSaltFormat
|
||||
olcPasswordCryptSaltFormat: $$6$$%.16s
|
||||
|
||||
dn: olcDatabase={1}mdb,cn=config
|
||||
changetype: modify
|
||||
add: olcDbIndex
|
||||
olcDbIndex: cn,ou,uid,mail eq
|
||||
endef
|
||||
|
||||
export define LDIF_ADD_DATA
|
||||
dn: $(AD_BASE)
|
||||
objectClass: organization
|
||||
objectClass: dcObject
|
||||
dc: $(AD_DC)
|
||||
o: $(AD_DOM)
|
||||
|
||||
dn: ou=$(AD_USR_OU),$(AD_BASE)
|
||||
ou: $(AD_USR_OU)
|
||||
objectClass: organizationalUnit
|
||||
|
||||
dn: ou=$(AD_GRP_OU),$(AD_BASE)
|
||||
ou: $(AD_GRP_OU)
|
||||
objectClass: organizationalUnit
|
||||
|
||||
dn: cn=$(AD_GRP_CN),ou=$(AD_GRP_OU),$(AD_BASE)
|
||||
cn: $(AD_GRP_CN)
|
||||
objectClass: groupOfNames
|
||||
objectClass: kopano-group
|
||||
member: uid=$(AD_ADM_CN),ou=$(AD_USR_OU),$(AD_BASE)
|
||||
member: uid=$(AD_USR_CN),ou=$(AD_USR_OU),$(AD_BASE)
|
||||
mail: $(AD_GRP_CN)@$(MAIL_DOMAIN)
|
||||
|
||||
dn: uid=$(AD_ADM_CN),ou=$(AD_USR_OU),$(AD_BASE)
|
||||
changetype: add
|
||||
cn: $(AD_ADM_CN)
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: kopano-user
|
||||
sn: $(AD_ADM_CN)
|
||||
uid: $(AD_ADM_CN)
|
||||
mail: $(AD_ADM_CN)@$(MAIL_DOMAIN)
|
||||
userPassword: $(AD_ADM_PW)
|
||||
telephoneNumber: $(AD_ADM_TEL)
|
||||
title: $(AD_ADM_TIT)
|
||||
kopanoAccount: 1
|
||||
kopanoAdmin: 1
|
||||
kopanoEnabledFeatures: imap
|
||||
kopanoEnabledFeatures: pop3
|
||||
|
||||
dn: uid=$(AD_USR_CN),ou=$(AD_USR_OU),$(AD_BASE)
|
||||
changetype: add
|
||||
cn: $(AD_USR_CN)
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: kopano-user
|
||||
sn: $(AD_USR_CN)
|
||||
uid: $(AD_USR_CN)
|
||||
mail: $(AD_USR_CN)@$(MAIL_DOMAIN)
|
||||
userPassword: $(AD_USR_PW)
|
||||
telephoneNumber: $(AD_USR_TEL)
|
||||
title: $(AD_USR_TIT)
|
||||
kopanoAccount: 1
|
||||
kopanoAliases: $(AD_USR_AS)@$(MAIL_DOMAIN)
|
||||
kopanoEnabledFeatures: imap
|
||||
kopanoEnabledFeatures: pop3
|
||||
|
||||
dn: uid=$(AD_SHR_CN),ou=$(AD_USR_OU),$(AD_BASE)
|
||||
cn: $(AD_SHR_CN)
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: kopano-user
|
||||
sn: $(AD_SHR_CN)
|
||||
uid: $(AD_SHR_CN)
|
||||
mail: $(AD_SHR_CN)@$(MAIL_DOMAIN)
|
||||
kopanoAccount: 1
|
||||
kopanoSharedStoreOnly: 1
|
||||
|
||||
dn: uid=$(AD_PUB_CN),ou=$(AD_USR_OU),$(AD_BASE)
|
||||
cn: $(AD_PUB_CN)
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: kopano-user
|
||||
sn: $(AD_PUB_CN)
|
||||
uid: $(AD_PUB_CN)
|
||||
mail: $(AD_PUB_CN)@$(MAIL_DOMAIN)
|
||||
kopanoAccount: 1
|
||||
kopanoHidden: 1
|
||||
kopanoSharedStoreOnly: 1
|
||||
kopanoResourceType: publicFolder:Public Stores/public
|
||||
endef
|
||||
|
||||
define smtp_mail
|
||||
@printf "From: <$(2)>\nTo: <$(3)>\nDate: $$(date -R)\nSubject: $(4)\
|
||||
\n\nGreat news! You can receive email.\n" | tee /dev/tty \
|
||||
| $(CURL_CMD) $(1) -T - --mail-from $(2) --mail-rcpt $(3) $(CURL_OPT)
|
||||
endef
|
||||
|
||||
define lmtp_mail
|
||||
printf "LHLO mx\nMAIL FROM: <$(2)>\nRCPT TO: <$(3)>\nDATA\
|
||||
\nFrom: <$(2)>\nTo: <$(3)>\nDate: $$(date -R)\nSubject: $(4)\
|
||||
\n\nGreat news! You can receive email.\n.\nQUIT\n" | tee /dev/tty \
|
||||
| $(CURL_CMD) $(1) -T - $(CURL_OPT)
|
||||
endef
|
||||
|
||||
export define MAKE_UTILS_CONTAINER
|
||||
CURL_CMD ?= docker run -i --rm --network $(NET_NAME) curlimages/curl
|
||||
webb_cmd ?= docker run -d --rm --network $(NET_NAME) \
|
||||
-e DISPLAY=$$$$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
-v /etc/localtime:/etc/localtime:ro -v $$$$(pwd)/ssl:/ssl \
|
||||
kennethkl/firefox $$(1)
|
||||
APP_FQDN ?= $(APP_NAME)
|
||||
AUT_FQDN ?= $(AUT_NAME)
|
||||
AUW_FQDN ?= $(AUW_NAME)
|
||||
DB_FQDN ?= $(DB_NAME)
|
||||
DBW_FQDN ?= $(DBW_NAME)
|
||||
MTA_FQDN ?= $(MTA_NAME)
|
||||
endef
|
||||
|
||||
utils-container:
|
||||
echo "$$MAKE_UTILS_CONTAINER" > utils-container.mk
|
||||
|
||||
utils-default:
|
||||
rm -f utils-container.mk
|
||||
|
||||
auth-mod_conf:
|
||||
echo "$$LDIF_MOD_CONF" | docker-compose exec -T auth ldapmodify -Q
|
||||
|
||||
auth-add_data:
|
||||
echo "$$LDIF_ADD_DATA" | docker-compose exec -T auth ldapadd -Q
|
||||
|
||||
auth-add_schema:
|
||||
docker-compose exec app zcat /usr/share/doc/kopano/kopano.ldif.gz \
|
||||
| docker-compose exec -T auth ldapadd -Q
|
||||
|
||||
auth-show_conf:
|
||||
docker-compose exec auth ldapsearch -QLLLb cn=config "(cn=config)"
|
||||
docker-compose exec auth ldapsearch -QLLLb cn=config olcDatabase={-1}frontend
|
||||
docker-compose exec auth ldapsearch -QLLLb cn=config olcDatabase={1}mdb
|
||||
|
||||
auth-show_data:
|
||||
docker-compose exec auth ldapsearch -QLLL
|
||||
|
||||
auth-show_cat0:
|
||||
docker-compose exec auth slapcat -n0
|
||||
|
||||
auth-show_cat1:
|
||||
docker-compose exec auth slapcat -n1
|
||||
|
||||
auth-web: auth-web-up
|
||||
sleep 2
|
||||
$(call webb_cmd,http://$(AUW_FQDN))
|
||||
|
||||
auth-web-up:
|
||||
docker run -d --name $(AUW_NAME) --network $(NET_NAME) \
|
||||
-e PHPLDAPADMIN_LDAP_HOSTS=auth -e PHPLDAPADMIN_HTTPS=false \
|
||||
osixia/phpldapadmin || true
|
||||
|
||||
auth-web-down:
|
||||
docker rm -f $(AUW_NAME) || true
|
||||
|
||||
mta-init:
|
||||
|
||||
mta-bayes:
|
||||
docker-compose exec mta sh -c 'rm -f bayesian.database.gz && wget http://artinvoice.hu/spams/bayesian.database.gz && gunzip bayesian.database.gz && sa-learn --restore bayesian.database && chown -R amavis: /var/amavis/.spamassassin && rm -rf bayesian.database'
|
||||
|
||||
mta-test_smtp:
|
||||
$(call smtp_mail,smtp://$(MTA_FQDN),$(MAIL_FROM),$(AD_USR_CN)@$(MAIL_DOMAIN),A SMTP test message.)
|
||||
|
||||
mta-test_regexp:
|
||||
$(call smtp_mail,smtp://$(MTA_FQDN),$(MAIL_FROM),$(AD_USR_CN)+info@$(MAIL_DOMAIN),A regexp SMTP test message.)
|
||||
|
||||
mta-test_smtps:
|
||||
$(call smtp_mail,smtps://$(MTA_FQDN),$(MAIL_FROM),$(AD_USR_CN)@$(MAIL_DOMAIN),A secure SMTPS test message.) \
|
||||
-k -u $(AD_USR_CN):$(AD_USR_PW)
|
||||
|
||||
mta-test_shared: all-test_quiet
|
||||
$(call smtp_mail,smtp://$(MTA_FQDN),$(MAIL_FROM),$(AD_SHR_CN)@$(MAIL_DOMAIN),A shared SMTP test message.)
|
||||
|
||||
mta-test_public: all-test_quiet
|
||||
$(call smtp_mail,smtp://$(MTA_FQDN),$(MAIL_FROM),$(AD_PUB_CN)@$(MAIL_DOMAIN),A public SMTP test message.)
|
||||
|
||||
mta-razor:
|
||||
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 %3s %s\n" $$(apk info -s $$pkg 2>/dev/null | sed -n "2{p;q}") $$pkg; done | sort | sort -k 2,2'
|
||||
|
||||
mta-quarantine_list:
|
||||
docker-compose exec mta amavis-ls
|
||||
|
||||
mta-freshclam_nodns:
|
||||
docker-compose exec mta freshclam --no-dns
|
||||
|
||||
mta-clamdtop:
|
||||
docker-compose exec mta clamdtop
|
||||
|
||||
mta-tools:
|
||||
docker-compose exec mta apk --no-cache --update add \
|
||||
nano less lsof htop openldap-clients bind-tools iputils strace iproute2
|
||||
|
||||
mta-htop: mta-tools
|
||||
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)
|
||||
|
||||
mta-show_doveconf:
|
||||
docker-compose exec mta doveconf -n
|
||||
|
||||
mta-show_postconf:
|
||||
docker-compose exec mta postconf -n
|
||||
|
||||
mta-show_mailq:
|
||||
docker-compose exec mta mailq
|
||||
|
||||
mta-flush_mailq:
|
||||
docker-compose exec mta postqueue -f
|
||||
|
||||
mta-test_auth:
|
||||
docker-compose exec mta doveadm auth test $(AD_USR_CN) $(AD_USR_PW)
|
||||
|
||||
mta-test_imaps:
|
||||
$(CURL_CMD) imaps://$(MTA_FQDN)//inbox -X "fetch 1 all" \
|
||||
--ssl --anyauth -k -u $(AD_USR_CN):$(AD_USR_PW) $(CURL_OPT)
|
||||
|
||||
mta-man:
|
||||
docker-compose exec mta apk --no-cache --update add man-db man-pages \
|
||||
postfix-doc cyrus-sasl-doc dovecot-doc spamassassin-doc clamav-doc razor-doc
|
||||
|
||||
db-init:
|
||||
|
||||
db-test:
|
||||
docker-compose exec db mysqlshow -u $(MYSQL_USER) $(MYSQL_DATABASE) -p$(MYSQL_PASSWORD)
|
||||
|
||||
db-web: db-web-up
|
||||
sleep 2
|
||||
$(call webb_cmd,http://$(DBW_FQDN))
|
||||
|
||||
db-web-up:
|
||||
docker run -d --name $(DBW_NAME) --network $(NET_NAME) \
|
||||
-e PMA_HOST=db phpmyadmin/phpmyadmin || true
|
||||
|
||||
db-web-down:
|
||||
docker rm -f $(DBW_NAME) || true
|
||||
|
||||
app-init: app-public_store app-create_smime
|
||||
|
||||
app-tools:
|
||||
docker-compose exec app apt-get update
|
||||
docker-compose exec app apt-get install --yes \
|
||||
less nano ldap-utils htop net-tools lsof iputils-ping dnsutils strace
|
||||
|
||||
app-htop: app-tools
|
||||
docker-compose exec app htop
|
||||
|
||||
app-test_smtp: mta-test_smtp
|
||||
|
||||
app-test_lmtp:
|
||||
$(call lmtp_mail,telnet://$(APP_FQDN):2003,$(MAIL_FROM),$(AD_USR_CN)@$(MAIL_DOMAIN),A LMTP test message.)
|
||||
|
||||
app-test_all: all-test_muted $(addprefix app-test_,imap pop3 ical imaps pop3s icals)
|
||||
|
||||
app-test_imap:
|
||||
$(CURL_CMD) imap://$(APP_FQDN) -u $(AD_USR_CN):$(AD_USR_PW) $(CURL_OPT)
|
||||
|
||||
app-test_imaps:
|
||||
$(CURL_CMD) imaps://$(APP_FQDN) -k -u $(AD_USR_CN):$(AD_USR_PW) $(CURL_OPT)
|
||||
|
||||
app-test_pop3:
|
||||
$(CURL_CMD) pop3://$(APP_FQDN) -u $(AD_USR_CN):$(AD_USR_PW) $(CURL_OPT)
|
||||
|
||||
app-test_pop3s:
|
||||
$(CURL_CMD) pop3s://$(APP_FQDN) -k -u $(AD_USR_CN):$(AD_USR_PW) $(CURL_OPT)
|
||||
|
||||
app-test_ical:
|
||||
$(CURL_CMD) http://$(APP_FQDN):8080 -u $(AD_USR_CN):$(AD_USR_PW) $(CURL_OPT)
|
||||
|
||||
app-test_icals:
|
||||
$(CURL_CMD) https://$(APP_FQDN):8443 -k -u $(AD_USR_CN):$(AD_USR_PW) $(CURL_OPT)
|
||||
|
||||
app-test_tls:
|
||||
$(TSSL_CMD) $(APP_FQDN):993 || true
|
||||
|
||||
app-web:
|
||||
$(call webb_cmd,http://$(APP_FQDN))
|
||||
|
||||
app-test_oof1:
|
||||
docker-compose exec app kopano-oof -u $(AD_USR_CN) -m 1 -t "Dunno when I return"
|
||||
|
||||
app-test_oof0:
|
||||
docker-compose exec app kopano-oof -u $(AD_USR_CN) -m 0
|
||||
|
||||
app-show_user1:
|
||||
docker-compose exec app kopano-admin --details $(AD_USR_CN)
|
||||
|
||||
app-show_user2: app-tools
|
||||
docker-compose exec app ldapsearch -H ldap://auth:389 -xLLL -b $(AD_BASE) '*'
|
||||
|
||||
app-show_sync:
|
||||
docker-compose exec app z-push-top
|
||||
|
||||
app-create_store:
|
||||
docker-compose exec app kopano-admin --create-store $(AD_USR_CN)
|
||||
|
||||
app-public_store:
|
||||
docker-compose exec app kopano-storeadm -P
|
||||
|
||||
#app-add_user:
|
||||
# docker-compose exec app kopano-admin -c $(AD_USR_CN) -p $(AD_USR_PW) \
|
||||
# -e $(AD_USR_CN)@$(MAIL_DOMAIN) -f $(AD_USR_CN) -a 1
|
||||
|
||||
$(addprefix app-parms_,archiver dagent gateway ical ldap search server spamd spooler):
|
||||
docker-compose exec app run list_parms $(patsubst app-parms_%,%,$@)
|
||||
|
||||
app-create_smime: all-create_smime
|
||||
docker cp ssl/ca.crt $(call dkr_srv_cnt,app):/usr/local/share/ca-certificates/$(MAIL_DOMAIN)_CA.crt
|
||||
docker-compose exec app update-ca-certificates
|
||||
|
||||
all-test_quiet:
|
||||
$(eval CURL_OPT := -s -S )
|
||||
|
||||
all-test_muted:
|
||||
$(eval CURL_OPT := -s -S >/dev/null || true)
|
||||
|
||||
all-create_smime: ssl/$(AD_USR_CN).p12
|
||||
|
||||
all-destroy_smime: ssl-destroy
|
|
@ -0,0 +1 @@
|
|||
../test/ad.mk
|
|
@ -0,0 +1 @@
|
|||
../test/dkr.mk
|
|
@ -0,0 +1,124 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
app:
|
||||
image: mlan/kopano
|
||||
networks:
|
||||
- backend
|
||||
# ports: # Uncomment to expose ports to host interfaces
|
||||
# - "80:80" # WebApp & EAS (alt. HTTP)
|
||||
# - "143:143" # IMAP (not needed if all devices can use EAS)
|
||||
# - "110:110" # POP3 (not needed if all devices can use EAS)
|
||||
# - "8080:8080" # ICAL (not needed if all devices can use EAS)
|
||||
# - "993:993" # IMAPS (not needed if all devices can use EAS)
|
||||
# - "995:995" # POP3S (not needed if all devices can use EAS)
|
||||
# - "8443:8443" # ICALS (not needed if all devices can use EAS)
|
||||
depends_on:
|
||||
- auth
|
||||
- db
|
||||
- mta
|
||||
environment: # Virgin config, ignored on restarts unless FORCE_CONFIG given.
|
||||
- USER_PLUGIN=ldap
|
||||
- LDAP_URI=ldap://auth/
|
||||
- MYSQL_HOST=db
|
||||
- SMTP_SERVER=mta
|
||||
- LDAP_SEARCH_BASE=${AD_BASE-dc=example,dc=com}
|
||||
- LDAP_USER_TYPE_ATTRIBUTE_VALUE=${AD_USR_OB-kopano-user}
|
||||
- LDAP_GROUP_TYPE_ATTRIBUTE_VALUE=${AD_GRP_OB-kopano-group}
|
||||
- LDAP_GROUPMEMBERS_ATTRIBUTE_TYPE=dn
|
||||
- LDAP_PROPMAP=
|
||||
- DAGENT_PLUGINS=movetopublicldap
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE-kopano}
|
||||
- MYSQL_USER=${MYSQL_USER-kopano}
|
||||
- MYSQL_PASSWORD=${MYSQL_PASSWORD-secret}
|
||||
- IMAP_LISTEN=*:143 # also listen to eth0
|
||||
- POP3_LISTEN=*:110 # also listen to eth0
|
||||
- ICAL_LISTEN=*:8080 # also listen to eth0
|
||||
- IMAPS_LISTEN=*:993 # enable TLS
|
||||
- POP3S_LISTEN=*:995 # enable TLS
|
||||
- ICALS_LISTEN=*:8443 # enable TLS
|
||||
- PLUGIN_SMIME_USER_DEFAULT_ENABLE_SMIME=true
|
||||
- SYSLOG_LEVEL=${SYSLOG_LEVEL-3}
|
||||
- LOG_LEVEL=${LOG_LEVEL-3}
|
||||
volumes:
|
||||
- app-conf:/etc/kopano
|
||||
- app-atch:/var/lib/kopano/attachments
|
||||
- app-sync:/var/lib/z-push
|
||||
- app-spam:/var/lib/kopano/spamd # kopano-spamd integration
|
||||
- /etc/localtime:/etc/localtime:ro # Use host timezone
|
||||
cap_add: # helps debugging by allowing strace
|
||||
- sys_ptrace
|
||||
|
||||
mta:
|
||||
image: mlan/postfix-amavis
|
||||
hostname: ${MAIL_SRV-mx}.${MAIL_DOMAIN-example.com}
|
||||
networks:
|
||||
- backend
|
||||
# ports: # # Uncomment to expose ports to host interfaces
|
||||
# - "25:25" # SMTP
|
||||
# - "465:465" # SMTPS authentication required
|
||||
depends_on:
|
||||
- auth
|
||||
environment: # Virgin config, ignored on restarts unless FORCE_CONFIG given.
|
||||
- MESSAGE_SIZE_LIMIT=${MESSAGE_SIZE_LIMIT-25600000}
|
||||
- LDAP_HOST=auth
|
||||
- VIRTUAL_TRANSPORT=lmtp:app:2003
|
||||
- SMTP_RELAY_HOSTAUTH=${SMTP_RELAY_HOSTAUTH-}
|
||||
- SMTP_TLS_SECURITY_LEVEL=${SMTP_TLS_SECURITY_LEVEL-}
|
||||
- SMTP_TLS_WRAPPERMODE=${SMTP_TLS_WRAPPERMODE-no}
|
||||
- SMTPD_USE_TLS=yes
|
||||
- LDAP_USER_BASE=ou=${AD_USR_OU-users},${AD_BASE-dc=example,dc=com}
|
||||
- LDAP_QUERY_FILTER_USER=(&(objectclass=${AD_USR_OB-kopano-user})(mail=%s))
|
||||
- LDAP_QUERY_ATTRS_PASS=uid=user
|
||||
- REGEX_ALIAS=${REGEX_ALIAS-}
|
||||
- DKIM_SELECTOR=${DKIM_SELECTOR-default}
|
||||
- SA_TAG_LEVEL_DEFLT=${SA_TAG_LEVEL_DEFLT-2.0}
|
||||
- SA_DEBUG=${SA_DEBUG-0}
|
||||
- SYSLOG_LEVEL=${SYSLOG_LEVEL-}
|
||||
- LOG_LEVEL=${LOG_LEVEL-0}
|
||||
- RAZOR_REGISTRATION=${RAZOR_REGISTRATION-}
|
||||
volumes:
|
||||
- mta:/srv
|
||||
- app-spam:/var/lib/kopano/spamd # kopano-spamd integration
|
||||
- /etc/localtime:/etc/localtime:ro # Use host timezone
|
||||
cap_add: # helps debugging by allowing strace
|
||||
- sys_ptrace
|
||||
|
||||
db:
|
||||
image: mariadb
|
||||
command: ['--log_warnings=1']
|
||||
networks:
|
||||
- backend
|
||||
environment:
|
||||
- LANG=C.UTF-8
|
||||
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD-secret}
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE-kopano}
|
||||
- MYSQL_USER=${MYSQL_USER-kopano}
|
||||
- MYSQL_PASSWORD=${MYSQL_PASSWORD-secret}
|
||||
volumes:
|
||||
- db:/var/lib/mysql
|
||||
- /etc/localtime:/etc/localtime:ro # Use host timezone
|
||||
|
||||
auth:
|
||||
image: mlan/openldap
|
||||
networks:
|
||||
- backend
|
||||
command: --root-cn ${AD_ROOT_CN-admin} --root-pw ${AD_ROOT_PW-secret}
|
||||
environment:
|
||||
- LDAPBASE=${AD_BASE-dc=example,dc=com}
|
||||
- LDAPDEBUG=${AD_DEBUG-parse}
|
||||
volumes:
|
||||
- auth:/srv
|
||||
- /etc/localtime:/etc/localtime:ro # Use host timezone
|
||||
|
||||
networks:
|
||||
backend:
|
||||
|
||||
volumes:
|
||||
app-atch:
|
||||
app-conf:
|
||||
app-spam:
|
||||
app-sync:
|
||||
auth:
|
||||
db:
|
||||
mta:
|
|
@ -0,0 +1 @@
|
|||
../test/ssl.mk
|
|
@ -1,150 +0,0 @@
|
|||
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:
|
||||
|
|
@ -3,13 +3,12 @@
|
|||
# $DOCKER_REPO and $DOCKER_TAG are injected into the build environment
|
||||
|
||||
echo "hooks/build called with IMAGE_NAME=${DOCKER_REPO}:${DOCKER_TAG}, so we will run:"
|
||||
printenv
|
||||
#printenv
|
||||
|
||||
case "${DOCKER_TAG}" in
|
||||
core) bld_target=kopano-core ;;
|
||||
full) bld_target=kopano-full ;;
|
||||
debugtools) bld_target=kopano-debugtools ;;
|
||||
webapp) bld_target=kopano-webapp ;;
|
||||
core) bld_target=core ;;
|
||||
full) bld_target=full ;;
|
||||
webapp) bld_target=core-webapp ;;
|
||||
*)
|
||||
echo "NOTHING since we do not know how to build with tag=${DOCKER_TAG}"
|
||||
exit 1
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
# hooks/post_push
|
||||
|
||||
version=$(assets/kopano-webaddr.sh -VV)
|
||||
version=$(src/kopano/bin/kopano-webaddr.sh -VV)
|
||||
|
||||
_docker_add_tags() {
|
||||
local tag="$1"
|
||||
|
@ -12,10 +12,8 @@ _docker_add_tags() {
|
|||
|
||||
if [[ "$DOCKER_TAG" == "full" ]]; then
|
||||
_docker_add_tags latest
|
||||
_docker_add_tags latest-full
|
||||
_docker_add_tags ${version}
|
||||
_docker_add_tags ${version}-full
|
||||
_docker_add_tags full-${version}
|
||||
else
|
||||
_docker_add_tags latest-${DOCKER_TAG}
|
||||
_docker_add_tags ${version}-${DOCKER_TAG}
|
||||
_docker_add_tags ${DOCKER_TAG}-${version}
|
||||
fi
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
#!/usr/bin/env sh
|
||||
# Copyright (c) 2017 Brian 'redbeard' Harrington <redbeard@dead-city.org>
|
||||
#
|
||||
# acme-export.sh - A simple utility to explode a Traefik acme.json file into a
|
||||
# directory of certificates and a private key
|
||||
#
|
||||
# Usage - acme-export.sh /etc/traefik/acme.json /etc/ssl/
|
||||
#
|
||||
# Dependencies -
|
||||
# util-linux
|
||||
# openssl
|
||||
# jq
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
# Exit codes:
|
||||
# 1 - A component is missing or could not be read
|
||||
# 2 - There was a problem reading acme.json
|
||||
# 4 - The destination certificate directory does not exist
|
||||
# 8 - Missing private key
|
||||
|
||||
#set -o errexit
|
||||
##set -o pipefail
|
||||
#set -o nounset
|
||||
#set -o verbose
|
||||
|
||||
|
||||
#
|
||||
# configuration
|
||||
#
|
||||
. docker-common.sh
|
||||
|
||||
DOCKER_ACME_SSL_DIR=${DOCKER_ACME_SSL_DIR-/etc/ssl/acme}
|
||||
ACME_FILE=${ACME_FILE-/acme/acme.json}
|
||||
|
||||
CMD_DECODE_BASE64="base64 -d"
|
||||
|
||||
#
|
||||
# functions
|
||||
#
|
||||
usage() { echo "$(basename $0) <path to acme> <destination cert directory>" ;}
|
||||
|
||||
test_args() {
|
||||
# when called by inotifyd the first argument is the single character
|
||||
# event descriptor, lets drop it
|
||||
dc_log 7 "Called with args $@"
|
||||
[ $# -ge 0 ] && [ ${#1} -eq 1 ] && shift
|
||||
readonly acmefile="${1-$ACME_FILE}"
|
||||
readonly certdir="${2-$DOCKER_ACME_SSL_DIR}"
|
||||
}
|
||||
|
||||
test_dependencies() {
|
||||
# Allow us to exit on a missing jq binary
|
||||
if dc_is_installed jq; then
|
||||
dc_log 7 "The package jq is installed."
|
||||
else
|
||||
dc_log 4 "You must have the binary jq to use this."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_acmefile() {
|
||||
if [ ! -r "${acmefile}" ]; then
|
||||
dc_log 4 "There was a problem reading from (${acmefile}). We need to read this file to explode the JSON bundle... exiting."
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
test_certdir() {
|
||||
if [ ! -d "${certdir}" ]; then
|
||||
dc_log 4 "Path ${certdir} does not seem to be a directory. We need a directory in which to explode the JSON bundle... exiting."
|
||||
exit 4
|
||||
fi
|
||||
}
|
||||
|
||||
make_certdirs() {
|
||||
# If they do not exist, create the needed subdirectories for our assets
|
||||
# and place each in a variable for later use, normalizing the path
|
||||
mkdir -p "${certdir}/certs" "${certdir}/private"
|
||||
pdir="${certdir}/private"
|
||||
cdir="${certdir}/certs"
|
||||
}
|
||||
|
||||
bad_acme() {
|
||||
dc_log 4 "There was a problem parsing your acme.json file."
|
||||
exit 2
|
||||
}
|
||||
|
||||
read_letsencryptkey() {
|
||||
# look for key assuming acme v2 format
|
||||
priv=$(jq -e -r '.[].Account.PrivateKey' "${acmefile}" 2>/dev/null)
|
||||
if [ $? -eq 0 ]; then
|
||||
acmeversion=2
|
||||
dc_log 7 "Using acme v2 format, the PrivateKey was found in ${acmefile}"
|
||||
else
|
||||
# look for key assuming acme v1 format
|
||||
priv=$(jq -e -r '.Account.PrivateKey' "${acmefile}" 2>/dev/null)
|
||||
if [ $? -eq 0 ]; then
|
||||
acmeversion=1
|
||||
dc_log 7 "Using acme v1 format, the PrivateKey was found in ${acmefile}"
|
||||
else
|
||||
dc_log 4 "There didn't seem to be a private key in ${acmefile}. Please ensure that there is a key in this file and try again."
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
save_letsencryptkey() {
|
||||
local keyfile=${pdir}/letsencrypt.key
|
||||
printf -- \
|
||||
"-----BEGIN RSA PRIVATE KEY-----\n%s\n-----END RSA PRIVATE KEY-----\n" \
|
||||
${priv} | fold -w 65 | \
|
||||
openssl rsa -inform pem -out $keyfile 2>/dev/null
|
||||
if [ -e $keyfile ]; then
|
||||
dc_log 7 "PrivateKey is valid and saved in $keyfile"
|
||||
else
|
||||
dc_log 4 "PrivateKey appers NOT to be valid"
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
read_domains() {
|
||||
# Process the certificates for each of the domains in acme.json
|
||||
case $acmeversion in
|
||||
1) jq_filter='.Certificates[].Domain.Main' ;;
|
||||
2) jq_filter='.[].Certificates[].domain.main' ;;
|
||||
esac
|
||||
domains=$(jq -r $jq_filter $acmefile)
|
||||
if [ -n "$domains" ]; then
|
||||
dc_log 7 "Extracting private key and cert bundle for domains $domains."
|
||||
else
|
||||
dc_log 4 "Unable to find any domains in $acmefile."
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Traefik stores a cert bundle for each domain. Within this cert
|
||||
# bundle there is both proper the certificate and the Let's Encrypt CA
|
||||
#
|
||||
save_certs() {
|
||||
dc_log 5 "Extracting private keys and cert bundles in ${acmefile}"
|
||||
case $acmeversion in
|
||||
1)
|
||||
jq_crtfilter='.Certificates[] | select (.Domain.Main == $domain )| .Certificate'
|
||||
jq_keyfilter='.Certificates[] | select (.Domain.Main == $domain )| .Key'
|
||||
;;
|
||||
2)
|
||||
jq_crtfilter='.[].Certificates[] | select (.domain.main == $domain )| .certificate'
|
||||
jq_keyfilter='.[].Certificates[] | select (.domain.main == $domain )| .key'
|
||||
;;
|
||||
esac
|
||||
for domain in $domains; do
|
||||
crt=$(jq -e -r --arg domain "$domain" "$jq_crtfilter" $acmefile) || bad_acme
|
||||
echo "${crt}" | ${CMD_DECODE_BASE64} > "${cdir}/${domain}.crt"
|
||||
key=$(jq -e -r --arg domain "$domain" "$jq_keyfilter" $acmefile) || bad_acme
|
||||
echo "${key}" | ${CMD_DECODE_BASE64} > "${pdir}/${domain}.key"
|
||||
done
|
||||
}
|
||||
|
||||
#
|
||||
# Run command in ACME_POSTHOOK if it contain a valid command and runsvdir is running.
|
||||
#
|
||||
run_posthook() {
|
||||
if (pidof runsvdir >/dev/null && [ -n "$ACME_POSTHOOK" ] && command -v $ACME_POSTHOOK >/dev/null); then
|
||||
local out=$(eval "$ACME_POSTHOOK" 2>&1)
|
||||
[ -n "$out" ] && dc_log 7 "$ACME_POSTHOOK : $out"
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# run
|
||||
#
|
||||
test_args $@
|
||||
test_dependencies
|
||||
test_acmefile
|
||||
test_certdir
|
||||
read_letsencryptkey
|
||||
make_certdirs
|
||||
save_letsencryptkey
|
||||
read_domains
|
||||
save_certs
|
||||
run_posthook
|
|
@ -0,0 +1,58 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# 10-acme-common
|
||||
#
|
||||
# Define variables and functions used during container initialization.
|
||||
#
|
||||
# Variables defined in Dockerfile
|
||||
# DOCKER_ACME_SSL_DIR DOCKER_APPL_SSL_DIR
|
||||
#
|
||||
ACME_FILE=${ACME_FILE-/acme/acme.json}
|
||||
HOSTNAME=${HOSTNAME-$(hostname)}
|
||||
DOMAIN=${HOSTNAME#*.}
|
||||
DOCKER_APPL_SSL_CERT=${DOCKER_APPL_SSL_CERT-$DOCKER_APPL_SSL_DIR/cert.pem}
|
||||
DOCKER_APPL_SSL_KEY=${DOCKER_APPL_SSL_KEY-$DOCKER_APPL_SSL_DIR/priv_key.pem}
|
||||
DOCKER_ACME_SSL_H_CERT=$DOCKER_ACME_SSL_DIR/certs/${HOSTNAME}.crt
|
||||
DOCKER_ACME_SSL_H_KEY=$DOCKER_ACME_SSL_DIR/private/${HOSTNAME}.key
|
||||
DOCKER_ACME_SSL_D_CERT=$DOCKER_ACME_SSL_DIR/certs/${DOMAIN}.crt
|
||||
DOCKER_ACME_SSL_D_KEY=$DOCKER_ACME_SSL_DIR/private/${DOMAIN}.key
|
||||
|
||||
#
|
||||
# Setup monitoring of ACME_FILE
|
||||
#
|
||||
acme_monitor_tls_cert() {
|
||||
if (dc_is_installed jq && (dc_is_command inotifyd || dc_is_command inotifywait)); then
|
||||
if [ -s $ACME_FILE ]; then
|
||||
# run acme-extract.sh on cnt creation (and every time the json file changes)
|
||||
dc_log 5 "Setup ACME TLS certificate monitoring"
|
||||
if dc_is_command inotifyd; then
|
||||
docker-service.sh "-n acme $(which inotifyd) $(which acme-extract.sh) $ACME_FILE:c"
|
||||
else
|
||||
docker-service.sh "-n acme sh -c \"while $(which inotifywait) -e close_write $ACME_FILE; do $(which acme-extract.sh); done\""
|
||||
fi
|
||||
# acme-extract.sh reports to logger but it is yet to be started so this run will be quiet
|
||||
acme-extract.sh $ACME_FILE $DOCKER_ACME_SSL_DIR
|
||||
fi
|
||||
else
|
||||
dc_log 5 "Not all required pkgs installed so cannot setup ACME TLS certificate monitoring"
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Arrange sym-links to support both host and domain certificates.
|
||||
#
|
||||
acme_symlink_tls_cert() {
|
||||
if ([ -r $DOCKER_ACME_SSL_H_CERT ] && [ -r $DOCKER_ACME_SSL_H_KEY ]); then
|
||||
dc_log 5 "Setting up ACME TLS certificate for host $HOSTNAME"
|
||||
mkdir -p $DOCKER_APPL_SSL_DIR
|
||||
ln -sf $DOCKER_ACME_SSL_H_CERT $DOCKER_APPL_SSL_CERT
|
||||
ln -sf $DOCKER_ACME_SSL_H_KEY $DOCKER_APPL_SSL_KEY
|
||||
else
|
||||
if ([ -r $DOCKER_ACME_SSL_D_CERT ] && [ -r $DOCKER_ACME_SSL_D_KEY ]); then
|
||||
dc_log 5 "Setting up ACME TLS certificate for domain $DOMAIN"
|
||||
mkdir -p $DOCKER_APPL_SSL_DIR
|
||||
ln -sf $DOCKER_ACME_SSL_D_CERT $DOCKER_APPL_SSL_CERT
|
||||
ln -sf $DOCKER_ACME_SSL_D_KEY $DOCKER_APPL_SSL_KEY
|
||||
fi
|
||||
fi
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# 50-acme-monitor-tlscert
|
||||
#
|
||||
# Functions defined in:
|
||||
# 10-acme-common
|
||||
#
|
||||
|
||||
#
|
||||
# Setup ACME monitor and arrange certificate symbolic links
|
||||
#
|
||||
acme_monitor_tls_cert
|
||||
acme_symlink_tls_cert
|
|
@ -0,0 +1,146 @@
|
|||
#!/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 if command is in the path
|
||||
#
|
||||
dc_is_command() { [ -x "$(command -v $1)" ] ;}
|
||||
|
||||
#
|
||||
# Tests if pkgs are installed
|
||||
#
|
||||
dc_is_installed() {
|
||||
if dc_is_command apk; then
|
||||
ver_cmd="apk -e info"
|
||||
elif dc_is_command dpkg; then
|
||||
ver_cmd="dpkg -s"
|
||||
else
|
||||
dc_log 5 "No package manager found among: apk dpkg"
|
||||
fi
|
||||
for cmd in $@; do
|
||||
$ver_cmd $cmd > /dev/null 2>&1 || return 1
|
||||
done
|
||||
}
|
||||
|
||||
#
|
||||
# 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
|
||||
}
|
||||
|
||||
#
|
||||
# Print package versions
|
||||
#
|
||||
dc_pkg_versions() {
|
||||
local pkgs="$@"
|
||||
local len=$(echo $pkgs | tr " " "\n" | wc -L)
|
||||
local ver ver_cmd sed_flt
|
||||
local os=$(sed -rn 's/PRETTY_NAME="(.*)"/\1/p' /etc/os-release)
|
||||
local kern=$(uname -r)
|
||||
local host=$(uname -n)
|
||||
dc_log 5 $host $os $kern
|
||||
if dc_is_command apk; then
|
||||
ver_cmd="apk info -s"
|
||||
sed_flt="s/.*-(.*)-.*/\1/p"
|
||||
elif dc_is_command dpkg; then
|
||||
ver_cmd="dpkg -s"
|
||||
sed_flt="s/Version: ([[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+).*/\1/p"
|
||||
else
|
||||
dc_log 5 "No package manager found among: apk dpkg"
|
||||
fi
|
||||
for pkg in $pkgs; do
|
||||
ver=$($ver_cmd $pkg 2> /dev/null | sed -rn "$sed_flt")
|
||||
if [ -n "$ver" ]; then
|
||||
printf "\t%-${len}s\t%s\n" $pkg $ver
|
||||
fi
|
||||
done
|
||||
}
|
|
@ -0,0 +1,280 @@
|
|||
#!/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}
|
||||
DOCKER_CRONTAB_FILE=${DOCKER_CRONTAB_FILE-/etc/crontab}
|
||||
DOCKER_CRONTAB_ENV=${DOCKER_CRONTAB_ENV-CRONTAB_ENTRY}
|
||||
|
||||
#
|
||||
# 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.
|
||||
# -a all
|
||||
# -r readable
|
||||
# -w writable
|
||||
# -x executable
|
||||
#
|
||||
dc_cond_chown() {
|
||||
dc_log 7 "Called with args: $@"
|
||||
OPTIND=1
|
||||
local find_opts="! -perm -404"
|
||||
while getopts ":arwx" opts; do
|
||||
case "${opts}" in
|
||||
a) find_opts="";;
|
||||
r) find_opts="! -perm -404";;
|
||||
w) find_opts="! -perm -606";;
|
||||
x) find_opts="! -perm -505";;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND -1))
|
||||
local user=$1
|
||||
shift
|
||||
if id $user > /dev/null 2>&1; then
|
||||
for dir in $@; do
|
||||
if [ -n "$(find $dir ! -user $user $find_opts -print -exec chown -h $user: {} \;)" ]; then
|
||||
dc_log 5 "Changed owner to $user for some files in $dir"
|
||||
fi
|
||||
done
|
||||
else
|
||||
dc_log 3 "User $user is unknown."
|
||||
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
|
||||
}
|
||||
|
||||
#
|
||||
# Setup crontab entries
|
||||
#
|
||||
dc_crontab_entries() {
|
||||
local entries="$(eval echo \${!$DOCKER_CRONTAB_ENV*})"
|
||||
for entry in $entries; do
|
||||
[ -z "${changed+x}" ] && local changed= && sed -i '/^\s*[0-9*]/d' $DOCKER_CRONTAB_FILE
|
||||
echo "${!entry}" >> $DOCKER_CRONTAB_FILE
|
||||
dc_log 5 "Added entry ${!entry} in $DOCKER_CRONTAB_FILE"
|
||||
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 [ -f "$DOCKER_UNLOCK_FILE" ]; then
|
||||
rm $DOCKER_UNLOCK_FILE
|
||||
dc_log 5 "Removing unlock file, locking the configuration."
|
||||
elif [ -n "$FORCE_CONFIG" ]; then
|
||||
dc_log 5 "Configuration update was forced, since we got FORCE_CONFIG=$FORCE_CONFIG"
|
||||
else
|
||||
dc_log 5 "No unlock file found, so not touching configuration."
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# true if there is no unlock file or FORCE_CONFIG is not empty
|
||||
#
|
||||
dc_is_unlocked() { [ -f "$DOCKER_UNLOCK_FILE" ] || [ -n "$FORCE_CONFIG" ] ;}
|
|
@ -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' SIGTERM
|
||||
|
||||
#
|
||||
# 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
|
|
@ -0,0 +1,45 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# docker-runfunc.sh
|
||||
#
|
||||
# Allow functions to be accessed from the command line.
|
||||
#
|
||||
|
||||
#
|
||||
# Source common functions.
|
||||
#
|
||||
. docker-common.sh
|
||||
. docker-config.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 "$@"
|
|
@ -0,0 +1,120 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# docker-service.sh
|
||||
#
|
||||
. docker-common.sh
|
||||
|
||||
# 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}
|
||||
|
||||
#
|
||||
# Define helpers
|
||||
#
|
||||
usage() {
|
||||
cat <<-!cat
|
||||
NAME
|
||||
docker-service.sh
|
||||
|
||||
SYNOPSIS
|
||||
docker-service.sh [-d] [-f] [-h] [-l] [-n name] [-s file] [-q] command [args]
|
||||
|
||||
OPTIONS
|
||||
-d default down
|
||||
-f remove lingering pid file at start up
|
||||
-h print this text and exit
|
||||
-l activate logging (svlogd)
|
||||
-n name use this name instead of command
|
||||
-s file source file
|
||||
-q send stdout and stderr to /dev/null
|
||||
|
||||
EXAMPLES
|
||||
docker-service.sh "kopano-dagent -l" "-d kopano-grapi serve"
|
||||
"-q -s /etc/apache2/envvars apache2 -DFOREGROUND -DNO_DETACH -k start"
|
||||
|
||||
!cat
|
||||
}
|
||||
|
||||
base_name() { local base=${1##*/}; echo ${base%%.*} ;}
|
||||
|
||||
pid_name() {
|
||||
local dir_name=${1%%-*}
|
||||
local pid_name=${1##*-}
|
||||
echo "${DOCKER_RUN_DIR}/${dir_name}/${pid_name}.pid"
|
||||
}
|
||||
|
||||
add_opt() {
|
||||
if [ -z "$options" ]; then
|
||||
options=$1
|
||||
else
|
||||
options="$options,$1"
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Define main function
|
||||
#
|
||||
|
||||
init_service() {
|
||||
local redirstd=
|
||||
local clearpid=
|
||||
local sourcefile=
|
||||
local sv_name cmd runsv_dir svlog_dir sv_log sv_down sv_force options
|
||||
dc_log 7 "Called with args $@"
|
||||
OPTIND=1
|
||||
while getopts ":dfhln:s:q" opts; do
|
||||
case "${opts}" in
|
||||
d) sv_down="down"; add_opt "down";;
|
||||
f) sv_force="force"; add_opt "force";;
|
||||
h) usage; exit;;
|
||||
l) sv_log="log"; add_opt "log";;
|
||||
n) sv_name="${OPTARG}"; add_opt "name";;
|
||||
s) sourcefile=". ${OPTARG}"; add_opt "source";;
|
||||
q) redirstd="exec >/dev/null"; add_opt "quiet";;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND -1))
|
||||
cmd=$(which "$1")
|
||||
sv_name=${sv_name-$(base_name $1)}
|
||||
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
|
||||
dc_log 5 "Setting up ($sv_name) options ($options) args ($@)"
|
||||
mkdir -p $runsv_dir
|
||||
cat <<-!cat > $runsv_dir/run
|
||||
#!/bin/sh -e
|
||||
exec 2>&1
|
||||
$forcepid
|
||||
$redirstd
|
||||
$sourcefile
|
||||
exec $cmd $@
|
||||
!cat
|
||||
chmod +x $runsv_dir/run
|
||||
if [ -n "$sv_down" ]; then
|
||||
touch $runsv_dir/down
|
||||
fi
|
||||
if [ -n "$sv_log" ]; then
|
||||
mkdir -p $runsv_dir/log $svlog_dir
|
||||
cat <<-!cat > $runsv_dir/log/run
|
||||
#!/bin/sh
|
||||
exec svlogd -tt $svlog_dir
|
||||
!cat
|
||||
chmod +x $runsv_dir/log/run
|
||||
fi
|
||||
else
|
||||
dc_log 4 "Cannot find command."
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# run
|
||||
#
|
||||
|
||||
for cmd in "$@" ; do
|
||||
init_service $cmd
|
||||
done
|
|
@ -0,0 +1 @@
|
|||
docker-runfunc.sh
|
|
@ -0,0 +1,7 @@
|
|||
# /etc/cron.d/docker-crontab: crontab entries for docker
|
||||
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
|
||||
|
||||
# During initialization CRONTAB_ENTRY envvars will overwrite all cron commands
|
||||
# in this file. Cron commands start with * or digits with optional white space.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# 20-docker-print-versions
|
||||
#
|
||||
dc_pkg_versions kopano-common kopano-webapp z-push-kopano
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# 50-docker-crontab-entry
|
||||
#
|
||||
# Functions defined in:
|
||||
# docker-config.sh
|
||||
#
|
||||
#
|
||||
|
||||
#
|
||||
# Add crontab entries if the config is unlocked.
|
||||
#
|
||||
if dc_is_unlocked; then
|
||||
dc_crontab_entries
|
||||
fi
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# 50-docker-update-loglevel
|
||||
#
|
||||
# If SYSLOG_LEVEL is not empty update syslog level
|
||||
#
|
||||
dc_update_loglevel
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# 80-docker-lock-config
|
||||
#
|
||||
# Functions defined in:
|
||||
# docker-config.sh
|
||||
#
|
||||
#
|
||||
dc_lock_config
|
|
@ -8,8 +8,8 @@ _webroot="https://download.kopano.io/community"
|
|||
_debroot="http://repo.z-hub.io/z-push:"
|
||||
_component="core"
|
||||
_stage="final"
|
||||
_dist="debian"
|
||||
_rel="8"
|
||||
_dist="ubuntu"
|
||||
_rel="20.04"
|
||||
_arch="amd64"
|
||||
|
||||
#
|
||||
|
@ -36,8 +36,8 @@ webaddr_kopano() {
|
|||
debaddr_zpush() {
|
||||
local stage="${1-$_stage}"
|
||||
local debroot="${2-$_debroot}"
|
||||
local dist=$(captialize "${3-$_dist}")
|
||||
local rel=$(decimalise "${4-$_rel}")
|
||||
local dist=$(captialize "${3-$_dist}")
|
||||
local rel=$(decimalise "${4-$_rel}")
|
||||
echo "$debroot/$stage/${dist}_${rel}"
|
||||
}
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# 10-kopano-common
|
||||
#
|
||||
# Kopano now installs without any cfg files, so we just write custom values
|
||||
# into their target cfg file.
|
||||
#
|
||||
# Variables defined in Dockerfile
|
||||
# DOCKER_CONF_DIR1 DOCKER_CONF_DIR2 DOCKER_ACME_SSL_DIR DOCKER_APPL_SSL_DIR
|
||||
#
|
||||
|
||||
#
|
||||
# Configuration
|
||||
#
|
||||
DOCKER_CONF_DIR1=${DOCKER_CONF_DIR1-/etc/kopano}
|
||||
DOCKER_CONF_DIR2=${DOCKER_CONF_DIR2-/usr/share/z-push}
|
||||
DOCKER_MAN5_DIR=${DOCKER_MAN5_DIR-/usr/share/man/man5/}
|
||||
DOCKER_APPL_SSL_CERT=${DOCKER_APPL_SSL_CERT-$DOCKER_APPL_SSL_DIR/cert.pem}
|
||||
DOCKER_APPL_SSL_KEY=${DOCKER_APPL_SSL_KEY-$DOCKER_APPL_SSL_DIR/priv_key.pem}
|
||||
DOCKER_LDAP_PMAP_FILE=${DOCKER_LDAP_PMAP_FILE-/usr/share/kopano/ldap.propmap.cfg}
|
||||
DOCKER_LDAP_SERVICES=${DOCKER_LDAP_SERVICES-archiver dagent gateway ical ldap search server spamd spooler}
|
||||
sqlstate_cfg_file=$DOCKER_CONF_DIR2/backend/sqlstatemachine/config.php
|
||||
zpush_cfg_file=$DOCKER_CONF_DIR2/config.php
|
||||
webapp_cfg_file=$DOCKER_CONF_DIR1/webapp/config.php
|
||||
webapp_mdm_cfg_file=$DOCKER_CONF_DIR1/webapp/config-mdm.php
|
||||
webapp_smime_cfg_file=$DOCKER_CONF_DIR1/webapp/config-smime.php
|
||||
|
||||
|
||||
#
|
||||
# CLI commands
|
||||
#
|
||||
list_parms() {
|
||||
local services="$DOCKER_LDAP_SERVICES $DAGENT_PLUGINS"
|
||||
[ $# -ge 0 ] && services="$@"
|
||||
for service in $services; do
|
||||
[ $# -ne 1 ] && echo "# $service"
|
||||
local man_file=$(kopano_gen_filename_man $service)
|
||||
kopano_get_envvars_man $man_file
|
||||
kopano_get_envvars_ext $service
|
||||
[ $# -ne 1 ] && echo
|
||||
done
|
||||
}
|
||||
|
||||
#
|
||||
# Apply environment variables to configuration files.
|
||||
# Use all valid keys (variables) for a service to see if there is a envvar with
|
||||
# identical name, if so apply its value to the config file.
|
||||
# With kopano-core use man page files to lookup valid keys.
|
||||
# With kopano-webapp and z-push use installed config file to find valid keys.
|
||||
#
|
||||
kopano_apply_envvars_core() {
|
||||
kopano_enable_envvars_plugin
|
||||
for service in $DOCKER_LDAP_SERVICES $DAGENT_PLUGINS; do
|
||||
kopano_apply_envvars_cfg $service
|
||||
done
|
||||
}
|
||||
|
||||
kopano_apply_envvars_webapp() {
|
||||
kopano_apply_envvars_php $webapp_cfg_file
|
||||
kopano_apply_envvars_php $webapp_mdm_cfg_file
|
||||
kopano_apply_envvars_php $webapp_smime_cfg_file
|
||||
}
|
||||
|
||||
kopano_apply_envvars_zpush() {
|
||||
kopano_apply_envvars_php $sqlstate_cfg_file
|
||||
kopano_apply_envvars_php $zpush_cfg_file
|
||||
}
|
||||
|
||||
#
|
||||
# kopano_apply_envvars_cfg <service>
|
||||
#
|
||||
kopano_apply_envvars_cfg() {
|
||||
local service=$1
|
||||
local cfg_file=$(kopano_gen_filename_cfg $service)
|
||||
local man_file=$(kopano_gen_filename_man $service)
|
||||
local env_vars="$(kopano_get_envvars_ext $service)"
|
||||
if [ -f $man_file ]; then
|
||||
env_vars="$(kopano_get_envvars_man $man_file) $env_vars"
|
||||
else
|
||||
dc_log 6 "Could not find $man_file"
|
||||
fi
|
||||
if [ -e $cfg_file ]; then
|
||||
mv -f $cfg_file $cfg_file.bak
|
||||
fi
|
||||
for env_var in $env_vars; do
|
||||
kopano_set_envvars_cfg $service $env_var
|
||||
done
|
||||
}
|
||||
|
||||
kopano_apply_envvars_php() {
|
||||
local cfg_file=$1
|
||||
if [ -e $cfg_file ]; then
|
||||
local env_vars="$(kopano_get_envvars_php $cfg_file)"
|
||||
for env_var in $env_vars; do
|
||||
if [ -n "${!env_var}" ]; then
|
||||
[ -z "${changed+x}" ] && local changed= && cp -f $cfg_file $cfg_file.bak
|
||||
dc_log 5 "Setting ${env_var} = ${!env_var} in $cfg_file"
|
||||
sed -ri "s/(\s*define[('\"]+${env_var}['\",]+).+/\1 ${!env_var});/Ig" $cfg_file
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# kopano_set_envvars_cfg <service> <envvar name> [explicit parameter name]
|
||||
#
|
||||
kopano_set_envvars_cfg() {
|
||||
local service=$1
|
||||
local cfg_file=$(kopano_gen_filename_cfg $service)
|
||||
local env_var=$2
|
||||
local uniq_var=${service^^}_${env_var}
|
||||
local cfg_par="${3-$env_var =}"
|
||||
if [ -n "${!uniq_var+x}" ]; then
|
||||
local env_val="${!uniq_var}"
|
||||
elif [ -n "${!env_var+x}" ]; then
|
||||
local env_val="${!env_var}"
|
||||
fi
|
||||
if [ -n "${env_val+x}" ]; then
|
||||
dc_log 5 "Setting ${cfg_par,,} ${env_val} in $cfg_file"
|
||||
echo ${cfg_par,,} ${env_val} >> $cfg_file
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# External parameters
|
||||
# services without a man file or additional parameters are listed here.
|
||||
#
|
||||
kopano_get_envvars_ext() {
|
||||
local service=$1
|
||||
local vars
|
||||
case $service in
|
||||
movetopublicldap)
|
||||
vars="ldap_public_folder_attribute ldap_public_folder_attribute_token"
|
||||
;;
|
||||
esac
|
||||
echo $vars | tr "[:lower:]" "[:upper:]" | tr " " "\n" | sort -u
|
||||
}
|
||||
|
||||
#
|
||||
# Python plugins
|
||||
#
|
||||
kopano_enable_envvars_plugin() {
|
||||
for service in dagent spooler; do
|
||||
local envvar=${service^^}_PLUGINS
|
||||
for plugin in ${!envvar}; do
|
||||
local pyfile=$(find /usr/share/kopano* -name ${plugin}.py)
|
||||
if [ -z "$pyfile" ]; then
|
||||
dc_log 5 "Unable to locate ${service} python plugin ${plugin}"
|
||||
else
|
||||
dc_log 5 "Enabling ${service} python plugin ${plugin}"
|
||||
mkdir -p $DOCKER_CONF_DIR1/${service}
|
||||
ln -sf ${pyfile} $DOCKER_CONF_DIR1/${service}
|
||||
export ${service^^}_PLUGIN_PATH=$DOCKER_CONF_DIR1/${service}
|
||||
export ${service^^}_PLUGIN_ENABLED=yes
|
||||
fi
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
#
|
||||
# Helpers
|
||||
#
|
||||
kopano_gen_filename_man() { echo $DOCKER_MAN5_DIR/kopano-$1.cfg.5.gz ;}
|
||||
kopano_gen_filename_cfg() { echo $DOCKER_CONF_DIR1/$1.cfg ;}
|
||||
kopano_get_envvars_man() { zcat $1 | sed -r "/^\.SS/!d;{s/^\.SS (.*)/\U\1/g;s/,//g}" | sort -u ;}
|
||||
kopano_get_envvars_php() { sed -nr "/define\(/Is/.*define\(['\"](.*)['\"], .*/\1/Ip" $1 | sort -u ;}
|
||||
|
||||
#
|
||||
# Update SSL_CERTIFICATE_FILE and SSL_PRIVATE_KEY_FILE.
|
||||
#
|
||||
kopano_export_tls_cert() {
|
||||
if ([ -f "$DOCKER_APPL_SSL_CERT" ] && [ -f "$DOCKER_APPL_SSL_KEY" ]); then
|
||||
export SSL_CERTIFICATE_FILE=${SSL_CERTIFICATE_FILE-$DOCKER_APPL_SSL_CERT}
|
||||
export SSL_PRIVATE_KEY_FILE=${SSL_PRIVATE_KEY_FILE-$DOCKER_APPL_SSL_KEY}
|
||||
fi
|
||||
if ([ -f "$SSL_CERTIFICATE_FILE" ] && [ -f "$SSL_PRIVATE_KEY_FILE" ]); then
|
||||
dc_cond_chown $DOCKER_APPL_RUNAS $SSL_CERTIFICATE_FILE $SSL_PRIVATE_KEY_FILE
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Generate self signed certificate if any of IMAPS_LISTEN, POP3S_LISTEN or
|
||||
# ICALS_LISTEN is no-empty but no certificates are given.
|
||||
#
|
||||
kopano_generate_tls_cert() {
|
||||
for prot in IMAPS_LISTEN POP3S_LISTEN ICALS_LISTEN; do
|
||||
if [ -n "${!prot}" ]; then
|
||||
local secure="$prot=${!prot}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if ([ -z "$SSL_CERTIFICATE_FILE" ] && [ -n "$secure" ] && dc_is_installed openssl); then
|
||||
dc_log 4 "$secure, but no certs given, so generating self-signed cert for host $HOSTNAME"
|
||||
mkdir -p $DOCKER_APPL_SSL_DIR
|
||||
dc_tls_setup_selfsigned_cert $DOCKER_APPL_SSL_CERT $DOCKER_APPL_SSL_KEY
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# LDAP directives
|
||||
#
|
||||
kopano_apply_envvar_propmap() {
|
||||
local service=ldap
|
||||
local cfg_file=$(kopano_gen_filename_cfg $service)
|
||||
local env_var=LDAP_PROPMAP
|
||||
if [ -n "${!env_var+x}" ]; then
|
||||
if [ -z "${!env_var}" ]; then
|
||||
typeset "$env_var=$DOCKER_LDAP_PMAP_FILE"
|
||||
elif [ ! -e "${!env_var}" ]; then
|
||||
dc_log 5 "No ${!env_var} so copying $DOCKER_LDAP_PMAP_FILE there."
|
||||
cp -f $DOCKER_LDAP_PMAP_FILE ${!env_var}
|
||||
fi
|
||||
kopano_set_envvars_cfg $service $env_var '!propmap'
|
||||
fi
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# 30-kopano-migrate
|
||||
#
|
||||
# Try to make configs compatible with new version if MIGRATE_CONFIG is defined.
|
||||
# Set MIGRATE_CONFIG=1 2 3 to list of fixes or MIGRATE_CONFIG=all to attempt all fixes.
|
||||
#
|
||||
kopano_apply_migrate_fixes() {
|
||||
local applied
|
||||
if [ -n "$MIGRATE_CONFIG" ]; then
|
||||
for fix in ${MIGRATE_CONFIG/all/1 2}; do # list all fixes here
|
||||
case $fix in
|
||||
1) dc_replace /etc/kopano/webapp/config.php 'define("INSECURE_COOKIES", true);' 'define("SECURE_COOKIES", false);' ;;
|
||||
2)
|
||||
for plugin in smime mdm; do
|
||||
local cfg_file=$DOCKER_CONF_DIR1/webapp/config-$plugin.php
|
||||
local src_file=$DOCKER_SMPL_DIR1/webapp/config-$plugin.php
|
||||
[ ! -f "$cfg_file" ] && cp "$src_file" "$cfg_file"
|
||||
done
|
||||
;;
|
||||
*) fix= ;;
|
||||
esac
|
||||
if [ -n "$fix" ]; then
|
||||
applied="$applied $fix"
|
||||
fi
|
||||
done
|
||||
dc_log 5 "Applied fixes;$applied to configuration since MIGRATE_CONFIG=$MIGRATE_CONFIG"
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# run
|
||||
#
|
||||
kopano_apply_migrate_fixes
|
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# 50-kopano-config
|
||||
#
|
||||
# Functions defined in:
|
||||
# docker-config.sh 10-kopano-common
|
||||
#
|
||||
#
|
||||
|
||||
#
|
||||
# Configure Kopano if it is unlocked.
|
||||
#
|
||||
if dc_is_unlocked; then
|
||||
kopano_export_tls_cert
|
||||
kopano_generate_tls_cert
|
||||
kopano_export_tls_cert
|
||||
kopano_apply_envvars_core
|
||||
kopano_apply_envvars_webapp
|
||||
kopano_apply_envvars_zpush
|
||||
kopano_apply_envvar_propmap
|
||||
fi
|
|
@ -0,0 +1,32 @@
|
|||
# movetopublicldap.cfg
|
||||
#
|
||||
# Move mail to public ldap configuration
|
||||
#
|
||||
# This configuration file is used by the dagent plugin 'movetopublicldap.py'
|
||||
# The default location is '/etc/kopano/movetopublicldap.cfg'
|
||||
#
|
||||
|
||||
#
|
||||
# The plugin reads two coniguration files.
|
||||
# First it looks for parameters in '/etc/kopano/ldap.cfg' allowing
|
||||
# common conifuration parameters to be kept in on place.
|
||||
# Second it reads '/etc/kopano/movetopublicldap.cfg'
|
||||
#
|
||||
|
||||
#
|
||||
# Parameters that can be set in both files:
|
||||
#
|
||||
# ldap_uri =
|
||||
# ldap_starttls = no
|
||||
# ldap_search_base =
|
||||
# ldap_bind_user =
|
||||
# ldap_bind_passwd =
|
||||
# ldap_user_unique_attribute = uid
|
||||
#
|
||||
|
||||
#
|
||||
# Parameters that can only be set in ovetopublicldap.cfg:
|
||||
#
|
||||
# ldap_public_folder_attribute = kopanoResourceType
|
||||
# ldap_public_folder_attribute_token = publicFolder
|
||||
#
|
|
@ -0,0 +1,229 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
""" (c) 2019 Kopano
|
||||
|
||||
movetopublicldap.py
|
||||
|
||||
This is an LDAP lookup extension to the move to public plugin movetopublic.py.
|
||||
The changes to the original work are highlihgted below.
|
||||
|
||||
The move to public plugin moves incoming messages to a folder in the public
|
||||
store. If folders are missing they will be created.
|
||||
|
||||
A LDAP entry including:
|
||||
|
||||
kopanoAccount: 1
|
||||
kopanoResourceType: publicFolder:<public folder>
|
||||
|
||||
will have its email delivered to the public store in <public folder>.
|
||||
The token match is case sensitive and there must be a colon ':' separating
|
||||
the token and the public folder name. The folder name can contain space and
|
||||
sub folders, which are distinguished using forward slash '/'.
|
||||
So if we have 'kopanoResourceType: publicFolder:Public Stores/public' emails will
|
||||
be delivered to 'Public Folders/Public Stores/public'.
|
||||
|
||||
The parameters in /etc/kopano/ldap.cfg will be used for the LDAP query.
|
||||
The LDAP attribute holding the token and the token itself have the following
|
||||
default values, which can be modified in /etc/kopano/movetopublicldap.cfg
|
||||
if desired.
|
||||
|
||||
ldap_public_folder_attribute = kopanoResourceType
|
||||
ldap_public_folder_attribute_token = publicFolder
|
||||
|
||||
"""
|
||||
from sys import hexversion
|
||||
from MAPI.Util import GetPublicStore
|
||||
from MAPI.Struct import NEWMAIL_NOTIFICATION
|
||||
from MAPI import MAPI_UNICODE, MAPI_MODIFY, OPEN_IF_EXISTS, MDB_WRITE
|
||||
from MAPI.Tags import (PR_RECEIVED_BY_EMAIL_ADDRESS_W, PR_EC_COMPANY_NAME_W,
|
||||
PR_IPM_PUBLIC_FOLDERS_ENTRYID, PR_ENTRYID, PR_MAILBOX_OWNER_ENTRYID,
|
||||
IID_IMessage, IID_IExchangeManageStore)
|
||||
from plugintemplates import IMapiDAgentPlugin, MP_CONTINUE, MP_STOP_SUCCESS
|
||||
from zconfig import ZConfigParser
|
||||
import configparser
|
||||
import ldap
|
||||
import os.path
|
||||
|
||||
class KConfigParser(ZConfigParser):
|
||||
""" Extends zconfig.ZConfigParser to also allow !directive in cfg files.
|
||||
Change type addition. """
|
||||
def __init__(self, configfile, defaultconfig={}):
|
||||
self.config = configparser.ConfigParser(defaults=defaultconfig,
|
||||
delimiters=('='), comment_prefixes=('#', '!'), allow_no_value=True)
|
||||
self.readZConfig(configfile)
|
||||
|
||||
class MoveToPublic(IMapiDAgentPlugin):
|
||||
|
||||
prioPreDelivery = 50
|
||||
|
||||
config = {}
|
||||
|
||||
CONFIGFILES = ['/etc/kopano/ldap.cfg', '/etc/kopano/movetopublicldap.cfg']
|
||||
|
||||
DEFAULTCONFIG = {
|
||||
'ldap_uri': None,
|
||||
'ldap_starttls': "no",
|
||||
'ldap_search_base': None,
|
||||
'ldap_bind_user': None,
|
||||
'ldap_bind_passwd': None,
|
||||
'ldap_user_unique_attribute': "uid",
|
||||
'ldap_public_folder_attribute': "kopanoResourceType",
|
||||
'ldap_public_folder_attribute_token': "publicFolder"
|
||||
}
|
||||
|
||||
def __init__(self, logger):
|
||||
IMapiDAgentPlugin.__init__(self, logger)
|
||||
self.readconfig(self.CONFIGFILES, self.DEFAULTCONFIG)
|
||||
|
||||
def readconfig(self, configfiles=CONFIGFILES, defaultconfig=DEFAULTCONFIG):
|
||||
""" Reads ldap.cfg and movetopublicldap.cfg into self.config.
|
||||
Change type addition. """
|
||||
options = [opt.split('_', 1)[1] for opt in defaultconfig.keys()]
|
||||
config = None
|
||||
for configfile in configfiles:
|
||||
if os.path.isfile(configfile):
|
||||
self.logger.logDebug("*--- Reading file {}".format(configfile))
|
||||
if not config:
|
||||
config = KConfigParser(configfile, defaultconfig)
|
||||
else:
|
||||
config = KConfigParser(configfile, config.options())
|
||||
else:
|
||||
self.logger.logDebug("*--- Can't find file {}".format(configfile))
|
||||
self.config = config.getdict('ldap',options)
|
||||
self.logger.logDebug("*--- Config list {}".format(self.config))
|
||||
return self.config
|
||||
|
||||
def searchfilter(self, recipient):
|
||||
""" (&(uid=recipient)(kopanoResourceType=publicFolder:*)).
|
||||
Change type addition. """
|
||||
return ("(&({}={})({}={}:*))"
|
||||
.format(self.config['user_unique_attribute'],
|
||||
recipient,
|
||||
self.config['public_folder_attribute'],
|
||||
self.config['public_folder_attribute_token']))
|
||||
|
||||
def searchquery(self, recipient):
|
||||
""" Query a LDAP/AD driectory server to lookup recipient using
|
||||
search_base and return public_folder_attribute.
|
||||
Change type addition. """
|
||||
if not self.config or not self.config['uri']:
|
||||
self.logger.logError(("!--- ldap_uri is not defined."
|
||||
" Please check {}" .format(self.CONFIGFILES)))
|
||||
return None
|
||||
else:
|
||||
l = ldap.initialize(self.config['uri'])
|
||||
try:
|
||||
l.protocol_version = ldap.VERSION3
|
||||
if self.config['starttls'] == 'yes':
|
||||
l.start_tls_s()
|
||||
l.simple_bind_s(self.config['bind_user'] or u'', \
|
||||
self.config['bind_passwd'] or u'')
|
||||
except ldap.SERVER_DOWN as e:
|
||||
self.logger.logError(("!--- LDAP server is not reachable {}"
|
||||
.format(e)))
|
||||
return None
|
||||
except ldap.INVALID_CREDENTIALS as e:
|
||||
self.logger.logError(("!--- Invalid LDAP credentials {}"
|
||||
" Please check {}" .format(e, self.CONFIGFILES)))
|
||||
l.unbind_s()
|
||||
return None
|
||||
except ldap.LDAPError as e:
|
||||
self.logger.logError("!--- LDAPError {}".format(e))
|
||||
l.unbind_s()
|
||||
return None
|
||||
try:
|
||||
result = l.search_s(self.config['search_base'], \
|
||||
ldap.SCOPE_SUBTREE, self.searchfilter(recipient), \
|
||||
[self.config['public_folder_attribute']])
|
||||
except ldap.LDAPError as e:
|
||||
self.logger.logError("!--- LDAPError {}".format(e))
|
||||
l.unbind_s()
|
||||
return result
|
||||
|
||||
def publicfolder(self, recipient):
|
||||
""" Check for ldap_public_folder_attribute_token and return folder.
|
||||
Change type addition. """
|
||||
destination_folder = []
|
||||
result = self.searchquery(recipient)
|
||||
if result:
|
||||
tokenandfolder = (result[0][1]
|
||||
.get(self.config['public_folder_attribute'])[0].decode('utf-8'))
|
||||
if tokenandfolder:
|
||||
destination_folder = tokenandfolder.split(':')[1]
|
||||
if destination_folder:
|
||||
self.logger.logDebug(("*--- Found public folder {}"
|
||||
"for recipient {}".format(
|
||||
destination_folder.encode('utf-8'),
|
||||
recipient.encode('utf-8'))))
|
||||
return destination_folder
|
||||
|
||||
def PreDelivery(self, session, addrbook, store, folder, message):
|
||||
""" Original code from movetopublic.py with call to self.publicfolder().
|
||||
Change type modification. """
|
||||
|
||||
props = message.GetProps([PR_RECEIVED_BY_EMAIL_ADDRESS_W], 0)
|
||||
if props[0].ulPropTag != PR_RECEIVED_BY_EMAIL_ADDRESS_W:
|
||||
self.logger.logError("!--- Not received by emailaddress")
|
||||
return MP_CONTINUE,
|
||||
|
||||
recipient = props[0].Value.lower()
|
||||
if not recipient:
|
||||
self.logger.logError("!--- No recipient in props {}".format(props))
|
||||
return MP_CONTINUE,
|
||||
|
||||
recipfolder = self.publicfolder(recipient)
|
||||
if not recipfolder:
|
||||
self.logger.logDebug(("*--- No public folder for recipient {}"
|
||||
.format(recipient.encode('utf-8'))))
|
||||
return MP_CONTINUE,
|
||||
|
||||
publicstore = GetPublicStore(session)
|
||||
if not publicstore:
|
||||
storeprops = store.GetProps([PR_MAILBOX_OWNER_ENTRYID], 0)
|
||||
if storeprops[0].ulPropTag == PR_MAILBOX_OWNER_ENTRYID:
|
||||
user = addrbook.OpenEntry(storeprops[0].Value, None, 0)
|
||||
userprops = user.GetProps([PR_EC_COMPANY_NAME_W], 0)
|
||||
if userprops[0].ulPropTag == PR_EC_COMPANY_NAME_W:
|
||||
companyname = userprops[0].Value
|
||||
else:
|
||||
companyname = None
|
||||
|
||||
if not companyname:
|
||||
self.logger.logError(("!--- Cannot open a public store."
|
||||
' Use "kopano-storeadm -P"'
|
||||
" to create one if it is missing."))
|
||||
return MP_CONTINUE,
|
||||
|
||||
ema = store.QueryInterface(IID_IExchangeManageStore)
|
||||
publicstoreid = ema.CreateStoreEntryID(None, companyname, MAPI_UNICODE)
|
||||
publicstore = session.OpenMsgStore(0, publicstoreid, None, MDB_WRITE)
|
||||
|
||||
publicfolders = publicstore.OpenEntry(
|
||||
publicstore.GetProps([PR_IPM_PUBLIC_FOLDERS_ENTRYID], 0)[0].Value,
|
||||
None, MAPI_MODIFY)
|
||||
|
||||
folderlist = recipfolder.split('/')
|
||||
folder = publicfolders
|
||||
for foldername in folderlist:
|
||||
if len(foldername) > 0:
|
||||
if hexversion >= 0x03000000:
|
||||
folder = folder.CreateFolder(0, foldername,
|
||||
"Create by Move to Public plugin", None,
|
||||
OPEN_IF_EXISTS | MAPI_UNICODE)
|
||||
else:
|
||||
folder = folder.CreateFolder(0, foldername,
|
||||
"Create by Move to Public plugin", None, OPEN_IF_EXISTS)
|
||||
|
||||
msgnew = folder.CreateMessage(None, 0)
|
||||
tags = message.GetPropList(MAPI_UNICODE)
|
||||
message.CopyProps(tags, 0, None, IID_IMessage, msgnew, 0)
|
||||
|
||||
msgnew.SaveChanges(0)
|
||||
folderid = folder.GetProps([PR_ENTRYID], 0)[0].Value
|
||||
msgid = msgnew.GetProps([PR_ENTRYID], 0)[0].Value
|
||||
|
||||
publicstore.NotifyNewMail(NEWMAIL_NOTIFICATION(msgid, folderid, 0, None, 0))
|
||||
|
||||
self.logger.logInfo(("*--- Message moved to public folder {}"
|
||||
.format(recipfolder)))
|
||||
|
||||
return MP_STOP_SUCCESS,
|
|
@ -0,0 +1,97 @@
|
|||
"""
|
||||
This code will query a LDAP/AD driectory server and updated the cgf file
|
||||
/etc/kopano/movetopublic.cfg
|
||||
/usr/share/kopano-dagent/python/plugins/movetopublic.cfg
|
||||
|
||||
export PYTHONPATH=/usr/share/kopano-dagent/python
|
||||
|
||||
"""
|
||||
from zconfig import ZConfigParser
|
||||
import configparser
|
||||
import ldap
|
||||
|
||||
class KConfigParser(ZConfigParser):
|
||||
""" allow !directive in cfg files """
|
||||
def __init__(self, configfile, defaultconfig={}):
|
||||
self.config = configparser.ConfigParser(defaults=defaultconfig, \
|
||||
delimiters=('='), comment_prefixes=('#', '!'))
|
||||
self.readZConfig(configfile)
|
||||
|
||||
class ldapstores():
|
||||
defaultconfig = {
|
||||
'ldap_uri': None,
|
||||
'ldap_search_base': None,
|
||||
'ldap_bind_user': None,
|
||||
'ldap_bind_passwd': None,
|
||||
'ldap_user_unique_attribute': "uid",
|
||||
'ldap_user_search_filter': "(kopanoAccount=1)",
|
||||
# 'ldap_user_search_filter': "(&(kopanoAccount=1)(kopanoResourceType=publicFolder:*))",
|
||||
'ldap_public_folder_attribute': "kopanoResourceType",
|
||||
'ldap_public_folder_attribute_token': "publicFolder"
|
||||
}
|
||||
|
||||
def __init__(self, configfile = '/etc/kopano/ldap.cfg'):
|
||||
self.readconfig(configfile)
|
||||
|
||||
def readconfig(self, configfile):
|
||||
config = KConfigParser(configfile, self.defaultconfig)
|
||||
options = [opt.split('_', 1)[1] for opt in self.defaultconfig.keys()]
|
||||
self.config = config.getdict('ldap',options)
|
||||
return self.config
|
||||
|
||||
def searchquery(self):
|
||||
if (self.config['uri'] is None):
|
||||
print ("ldap_uri is None")
|
||||
sys.exit(0)
|
||||
else:
|
||||
l = ldap.initialize(self.config['uri'])
|
||||
try:
|
||||
l.protocol_version = ldap.VERSION3
|
||||
l.simple_bind_s(self.config['bind_user'] or u'', \
|
||||
self.config['bind_passwd'] or u'')
|
||||
except ldap.INVALID_CREDENTIALS:
|
||||
sys.exit(0)
|
||||
except ldap.LDAPError as e:
|
||||
print (e)
|
||||
sys.exit(0)
|
||||
try:
|
||||
ldap_result_id = l.search(self.config['search_base'], \
|
||||
ldap.SCOPE_SUBTREE, self.config['user_search_filter'], \
|
||||
[self.config['user_unique_attribute'], \
|
||||
self.config['public_folder_attribute']])
|
||||
results = []
|
||||
while 1:
|
||||
result_type, result_data = l.result(ldap_result_id, 0)
|
||||
if (result_data == []):
|
||||
break
|
||||
else:
|
||||
if result_type == ldap.RES_SEARCH_ENTRY:
|
||||
results.append(result_data[0])
|
||||
except ldap.LDAPError as e:
|
||||
print (e)
|
||||
l.unbind_s()
|
||||
return results
|
||||
|
||||
def findpublic(self):
|
||||
stores = self.searchquery()
|
||||
public = []
|
||||
for store in stores:
|
||||
recipient = store[1].get(self.config['user_unique_attribute'])
|
||||
tokenandfolder = store[1].get(self.config['ldap_public_folder_attribute'])
|
||||
if tokenandfolder:
|
||||
token = tokenandfolder.split(':')[0]
|
||||
destination_folder = tokenandfolder.split(':')[1]
|
||||
if (token == self.config['ldap_public_folder_attribute_token']);
|
||||
public[recipient] = destination_folder
|
||||
return public
|
||||
|
||||
def printpublic(self, outputfile = '/etc/kopano/movetopublic.cfg'):
|
||||
public = self.findpublic()
|
||||
i = 1
|
||||
for recipient in public.keys():
|
||||
print ("rule%d_recipient = %s", i, recipient)
|
||||
print ("rule%d_destination_folder = %s", i, public[recipient])
|
||||
i += 1
|
||||
|
||||
s = ldapstores()
|
||||
s.printpublic()
|
|
@ -0,0 +1,318 @@
|
|||
# Makefile
|
||||
#
|
||||
# test
|
||||
#
|
||||
|
||||
-include *.mk
|
||||
|
||||
TST_REPO ?= mlan/kopano
|
||||
TST_VER ?= latest
|
||||
_ver = $(if $(findstring latest,$(1)),$(2),$(2)-$(1))
|
||||
|
||||
NET_NAME ?= test-net
|
||||
NET_ENV ?= --network $(NET_NAME)
|
||||
|
||||
AD_BASE ?= dc=example,dc=com
|
||||
AD_DOM ?= $(call ad_sub_dot, $(AD_BASE))
|
||||
AD_DC ?= $(call ad_cut_dot, 1, 1, $(AD_DOM))
|
||||
AD_GRP_OU ?= groups
|
||||
AD_USR_OB ?= kopano-user
|
||||
AD_USR_OU ?= users
|
||||
AD_USR_CN ?= hero
|
||||
AD_USR_PW ?= enigma
|
||||
|
||||
MAIL_SUB ?= ~~~test~message~~~
|
||||
MAIL_MSG ?= Enjoy!
|
||||
|
||||
WEB_TIT ?= "Kopano WebApp"
|
||||
|
||||
SQL_BASE ?= kopano
|
||||
SQL_USER ?= kopano
|
||||
SQL_PASS ?= secret
|
||||
SQL_ENV ?= \
|
||||
-e MYSQL_DATABASE=$(SQL_BASE) \
|
||||
-e MYSQL_USER=$(SQL_USER) \
|
||||
-e MYSQL_PASSWORD=$(SQL_PASS) \
|
||||
|
||||
APP_NAME ?= app
|
||||
APP_FQDN ?= $(APP_NAME).$(AD_DOM)
|
||||
APP_CERT ?= ssl/$(APP_FQDN).crt
|
||||
APP_KEY ?= ssl/$(APP_FQDN).key
|
||||
APP_VOL ?=
|
||||
APP_SLOG ?= 7
|
||||
APP_ALOG ?= 6
|
||||
APP_ENV ?= $(NET_ENV) $(SQL_ENV) \
|
||||
--name $(APP_NAME) \
|
||||
--hostname $(APP_FQDN) \
|
||||
-e MYSQL_HOST=$(DB_NAME) \
|
||||
-e USER_PLUGIN=ldap \
|
||||
-e LDAP_URI=ldap://$(AUT_NAME):389/ \
|
||||
-e LDAP_SEARCH_BASE=$(AD_BASE) \
|
||||
-e LDAP_USER_TYPE_ATTRIBUTE_VALUE=$(AD_USR_OB) \
|
||||
-e LDAP_GROUP_TYPE_ATTRIBUTE_VALUE=$(AD_GRP_OU) \
|
||||
-e LDAP_PROPMAP= \
|
||||
-e IMAP_LISTEN=*:143 \
|
||||
-e POP3_LISTEN=*:110 \
|
||||
-e ICAL_LISTEN=*:8080 \
|
||||
-e DISABLED_FEATURES= \
|
||||
-e LOGLEVEL=LOGLEVEL_DEBUG \
|
||||
-e SYSLOG_LEVEL=$(APP_SLOG) \
|
||||
-e LOG_LEVEL=$(APP_ALOG)
|
||||
APPS_ENV ?= $(APP_ENV) \
|
||||
-e IMAPS_LISTEN=*:993 \
|
||||
-e POP3S_LISTEN=*:995 \
|
||||
-e ICALS_LISTEN=*:8443
|
||||
APPA_ENV ?= $(APPS_ENV) \
|
||||
-v $(shell pwd)/acme:/acme
|
||||
|
||||
AUT_NAME ?= auth
|
||||
AUT_IMG ?= mlan/openldap
|
||||
AUT_FQDN ?= $(AUT_NAME).$(AD_DOM)
|
||||
AUT_VOL ?=
|
||||
AUT_ENV ?= $(NET_ENV) \
|
||||
--name $(AUT_NAME) \
|
||||
--hostname $(AUT_FQDN)
|
||||
|
||||
TAW_NAME ?= throwaway
|
||||
TAW_ENV ?= --entrypoint /bin/bash \
|
||||
--name $(TAW_NAME) \
|
||||
--rm
|
||||
|
||||
CURL_OPT ?= -s -v
|
||||
CURL_IMG ?= curlimages/curl
|
||||
CURL_ENV ?= $(NET_ENV) \
|
||||
-i --rm
|
||||
|
||||
DB_NAME ?= db
|
||||
DB_IMG ?= mariadb
|
||||
DB_FQDN ?= $(DB_NAME).$(AD_DOM)
|
||||
DB_VOL ?=
|
||||
DB_CMD ?= --log_warnings=1
|
||||
DB_ENV ?= $(NET_ENV) $(SQL_ENV) \
|
||||
--name $(DB_NAME) \
|
||||
--hostname $(DB_FQDN) \
|
||||
-e MYSQL_ROOT_PASSWORD=$(SQL_PASS)
|
||||
|
||||
GREP_ENV ?=
|
||||
|
||||
TST_W8S1 ?= 1
|
||||
TST_W8S2 ?= 20
|
||||
TST_W8L1 ?= 20
|
||||
TST_W8L2 ?= 120
|
||||
|
||||
export define LDIF_ADD_DATA
|
||||
dn: $(AD_BASE)
|
||||
objectClass: organization
|
||||
objectClass: dcObject
|
||||
dc: $(AD_DC)
|
||||
o: $(AD_DOM)
|
||||
|
||||
dn: ou=$(AD_USR_OU),$(AD_BASE)
|
||||
objectClass: organizationalUnit
|
||||
ou: $(AD_USR_OU)
|
||||
|
||||
dn: ou=$(AD_GRP_OU),$(AD_BASE)
|
||||
objectClass: organizationalUnit
|
||||
ou: $(AD_GRP_OU)
|
||||
|
||||
dn: uid=$(AD_USR_CN),ou=$(AD_USR_OU),$(AD_BASE)
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: $(AD_USR_OB)
|
||||
cn: $(AD_USR_CN)
|
||||
sn: $(AD_USR_CN)
|
||||
uid: $(AD_USR_CN)
|
||||
mail: $(AD_USR_CN)@$(AD_DOM)
|
||||
kopanoAccount: 1
|
||||
userPassword: $(AD_USR_PW)
|
||||
endef
|
||||
|
||||
|
||||
variables:
|
||||
make -pn | grep -A1 "^# makefile"| grep -v "^#\|^--" | sort | uniq
|
||||
|
||||
ps:
|
||||
docker ps -a
|
||||
|
||||
test-all: test-up_0 test_1 test_2 test_3
|
||||
|
||||
|
||||
test_%: test-up_% test-waitl_% test-logs_% test-service_% test-down_%
|
||||
|
||||
|
||||
test-pull:
|
||||
$(addprefix docker pull ,$(CURL_IMG); $(AUT_IMG); $(DB_IMG))
|
||||
|
||||
test-up_0: test-up-net
|
||||
#
|
||||
#
|
||||
#
|
||||
# test (0) run without envvars (is there smoke?)
|
||||
#
|
||||
# run containers see if there are logs and stop.
|
||||
#
|
||||
#
|
||||
docker run -d --name $(APP_NAME) $(TST_REPO):$(call _ver,$(TST_VER),core)
|
||||
sleep $(TST_W8L1)
|
||||
docker container logs $(APP_NAME) | grep 'docker-entrypoint.sh'
|
||||
docker rm -fv $(APP_NAME)
|
||||
sleep $(TST_W8S1)
|
||||
docker run -d --name $(APP_NAME) $(TST_REPO):$(call _ver,$(TST_VER),full)
|
||||
sleep $(TST_W8L1)
|
||||
docker container logs $(APP_NAME) | grep 'docker-entrypoint.sh'
|
||||
docker rm -fv $(APP_NAME)
|
||||
sleep $(TST_W8S1)
|
||||
#
|
||||
#
|
||||
# test (0) success ☺
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
test-up_1: test-up-net test-up-deps_1 test-up-auth_1
|
||||
#
|
||||
#
|
||||
#
|
||||
# test (1) ldap auth, sql db, and mail send recv
|
||||
#
|
||||
# send: curl lmtp://app -> srv mysql://db & srv ldap://auth
|
||||
# recv: curl pop3://app
|
||||
# recv: curl imap://app
|
||||
#
|
||||
#
|
||||
docker run -d $(APP_ENV) $(APP_VOL) $(TST_REPO):$(call _ver,$(TST_VER),core)
|
||||
|
||||
test-up_2: test-up-net test-up-deps_2 test-up-auth_2
|
||||
#
|
||||
#
|
||||
#
|
||||
# test (2) ldap auth, sql db, mail send recv secure using pem cert files
|
||||
#
|
||||
# send: curl lmtp://app -> srv mysql://db & srv ldap://auth
|
||||
# recv: curl pop3s://app
|
||||
# recv: curl imaps://app
|
||||
#
|
||||
#
|
||||
docker run -d $(APPS_ENV) $(APP_VOL) $(TST_REPO):$(call _ver,$(TST_VER),core)
|
||||
|
||||
test-up_3: test-up-net test-up-deps_3 test-up-auth_3 acme/acme.json
|
||||
#
|
||||
#
|
||||
#
|
||||
# test (3) ldap auth, sql db, mail send recv secure using acme cert
|
||||
#
|
||||
# send: curl lmtp://app -> srv mysql://db & srv ldap://auth
|
||||
# recv: curl pop3s://app
|
||||
# recv: curl imaps://app
|
||||
# web: curl http://app
|
||||
#
|
||||
#
|
||||
docker run -d $(APPA_ENV) $(APP_VOL) $(TST_REPO):$(call _ver,$(TST_VER),full)
|
||||
|
||||
test-up: test-up_1
|
||||
|
||||
test-service: test-service_0
|
||||
|
||||
test-service_%: test-pop3_% test-imap_% test-http_%
|
||||
#
|
||||
#
|
||||
# test ($*) success ☺
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
test-logs_%:
|
||||
-docker ps -a
|
||||
-docker container logs $(AUT_NAME)
|
||||
-docker container logs $(DB_NAME)
|
||||
-docker container logs $(APP_NAME) # | grep 'docker-entrypoint.sh'
|
||||
|
||||
test-waits_%:
|
||||
case $* in [1-4]) sleep $(TST_W8S1);; *) sleep $(TST_W8S2);; esac
|
||||
|
||||
test-waitl_%:
|
||||
case $* in [1-4]) sleep $(TST_W8L1);; *) sleep $(TST_W8L2);; esac
|
||||
|
||||
test-up-net:
|
||||
docker network create $(NET_NAME) 2>/dev/null || true
|
||||
|
||||
test-down-net:
|
||||
docker network rm $(NET_NAME) 2>/dev/null || true
|
||||
|
||||
test-down: test-down_0 test-down-net acme-destroy
|
||||
|
||||
|
||||
test-down_%:
|
||||
@docker rm -fv $(APP_NAME) $(DB_NAME) $(AUT_NAME) $(TAW_NAME) 2>/dev/null || true
|
||||
@if [ $* -ge 0 ]; then sleep $(TST_W8S1); fi
|
||||
|
||||
test-up-deps_%:
|
||||
docker run -d $(DB_ENV) $(DB_VOL) $(DB_IMG) $(DB_CMD)
|
||||
docker run -d $(AUT_ENV) $(AUT_VOL) $(AUT_IMG)
|
||||
|
||||
test-up-auth_%:
|
||||
sleep $(TST_W8L1)
|
||||
docker run $(TAW_ENV) $(TST_REPO):$(call _ver,$(TST_VER),core) \
|
||||
zcat /usr/share/doc/kopano/kopano.ldif.gz \
|
||||
| docker exec -i $(AUT_NAME) ldapadd -Q
|
||||
echo "$$LDIF_ADD_DATA" | docker exec -i $(AUT_NAME) ldapadd -Q
|
||||
|
||||
test-lmtp: test-lmtp_0
|
||||
test-lmtp_%:
|
||||
printf "LHLO mx\nMAIL FROM: <test@example.biz>\nRCPT TO: <$(AD_USR_CN)@$(AD_DOM)>\nDATA\nFrom: A tester <test@example.biz>\nTo: <$(AD_USR_CN)@$(AD_DOM)>\nDate: $$(date)\nSubject: $(MAIL_SUB)$*\n$(MAIL_MSG)$*\n.\nQUIT\n"\
|
||||
| nc -C $(call dkr_cnt_ip,$(APP_NAME)) 2003 # > /dev/null
|
||||
case $* in [1-3]) sleep $(TST_W8S1);; [4-9]) sleep $(TST_W8S2);; esac
|
||||
|
||||
test-cfg_%:
|
||||
$(eval cfg_s := $(shell [ $* -ge 2 ] && echo s))
|
||||
|
||||
# Note: cannot use prereq also in a proper target since it will change the
|
||||
# prereq order, eg. avoid the "3" in test-imaps: test-imap_3
|
||||
test-imap: test-imap_0
|
||||
test-imaps: test-imap_9
|
||||
test-imap_%: test-cfg_% test-lmtp_%
|
||||
docker run $(CURL_ENV) $(CURL_IMG) $(CURL_OPT) imap$(cfg_s)://$(APP_NAME)/inbox \
|
||||
--ssl --anyauth -k -X "fetch 1 all" -u $(AD_USR_CN):$(AD_USR_PW) | grep $(GREP_ENV) $(MAIL_SUB)$*
|
||||
|
||||
test-pop3: test-pop3_0
|
||||
test-pop3s: test-pop3_9
|
||||
test-pop3_%: test-cfg_% test-lmtp_%
|
||||
docker run $(CURL_ENV) $(CURL_IMG) $(CURL_OPT) pop3$(cfg_s)://$(APP_NAME)/1 \
|
||||
--ssl --anyauth -k -u $(AD_USR_CN):$(AD_USR_PW) | grep $(GREP_ENV) $(MAIL_SUB)$*
|
||||
|
||||
test-http: test-http_9
|
||||
test-http_%: test-cfg_%
|
||||
if [ $* -ge 3 ]; then \
|
||||
docker run $(CURL_ENV) $(CURL_IMG) $(CURL_OPT) http://$(APP_NAME) | grep $(GREP_ENV) $(WEB_TIT) \
|
||||
; fi
|
||||
|
||||
test-env:
|
||||
docker exec -it $(APP_NAME) env
|
||||
|
||||
test-logs:
|
||||
docker container logs $(APP_NAME)
|
||||
|
||||
test-sh:
|
||||
docker exec -it $(APP_NAME) sh -c 'exec $$(getent passwd root | sed "s/.*://g")'
|
||||
|
||||
test-diff:
|
||||
docker container diff $(APP_NAME)
|
||||
|
||||
test-sv:
|
||||
docker exec -it $(APP_NAME) sh -c 'sv status $$SVDIR/*'
|
||||
|
||||
test-debugtools:
|
||||
docker exec -it $(APP_NAME) apt-get update
|
||||
docker exec -it $(APP_NAME) apt-get install --yes \
|
||||
less nano ldap-utils htop net-tools lsof iputils-ping strace
|
||||
|
||||
test-htop: test-debugtools
|
||||
docker exec -it $(APP_NAME) htop
|
||||
|
||||
test-tls: #--starttls imap
|
||||
docker run --rm -it $(NET_ENV) drwetter/testssl.sh $(APP_NAME):993 || true
|
||||
|
||||
acme-destroy: ssl-destroy
|
||||
rm -f acme/*
|
||||
|
||||
acme/acme.json: $(APP_CERT)
|
||||
bin/gen-acme-json.sh $(AD_USR_CN)@$(AD_DOM) $(APP_FQDN) $(APP_KEY) $(APP_CERT) > $@
|
|
@ -0,0 +1,4 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
|
@ -0,0 +1,38 @@
|
|||
# ad.mk
|
||||
#
|
||||
# AD and LDAP make-functions
|
||||
#
|
||||
|
||||
#
|
||||
# chars
|
||||
#
|
||||
char_null :=
|
||||
char_space := $(char_null) #
|
||||
char_comma := ,
|
||||
char_dot := .
|
||||
char_colon := :
|
||||
|
||||
#
|
||||
# $(call ad_sub_dc,example.com) -> dc=example,dc=com
|
||||
#
|
||||
ad_sub_dc = $(subst $(char_space),$(char_comma),$(addprefix dc=, $(subst ., ,$(1))))
|
||||
#
|
||||
# $(call ad_sub_dot,dc=example,dc=com) -> example.com
|
||||
#
|
||||
ad_sub_dot = $(subst $(char_comma)dc=,$(char_dot),$(patsubst dc=%,%,$(1)))
|
||||
#
|
||||
# $(call ad_cat_dn,admin,dc=example,dc=com) -> cn=admin,dc=example,dc=com
|
||||
#
|
||||
ad_cat_dn = cn=$(1),$(2)
|
||||
#
|
||||
# $(call ad_cut_dot,1,1,example.com) -> example
|
||||
#
|
||||
ad_cut_dot = $(subst $(char_space),$(char_dot),$(wordlist $(1), $(2), $(subst $(char_dot),$(char_space),$(3))))
|
||||
#
|
||||
# $(call ad_rootdc,2,9,adm.dom.org:secret) -> dom.org
|
||||
#
|
||||
ad_rootdc = $(subst $(char_space),$(char_dot),$(wordlist $(1), $(2), $(subst $(char_dot),$(char_space),$(firstword $(subst $(char_colon),$(char_space),$(3))))))
|
||||
#
|
||||
# $(call ad_rootpw,adm.dom.org:secret) -> secret
|
||||
#
|
||||
ad_rootpw = $(lastword $(subst $(char_colon),$(char_space),$(1)))
|
|
@ -0,0 +1,44 @@
|
|||
#!/bin/bash
|
||||
# args: email hostname keyfile certfile
|
||||
mail=$1
|
||||
host=$2
|
||||
keyfile=$3
|
||||
certfile=$4
|
||||
|
||||
#
|
||||
# The "PrivateKey": attribute needs a PKCS#1 key without tags and line breaks
|
||||
# "openssl req -newkey rsa" generates a key stored in PKCS#8 so needs conversion
|
||||
#
|
||||
#acme_strip_tag() { openssl rsa -in $1 | sed '/^-----/d' | sed ':a;N;$!ba;s/\n//g' ;}
|
||||
acme_strip_tag() { sed '/^-----/d' $1 | sed ':a;N;$!ba;s/\n//g' ;}
|
||||
|
||||
cat <<-!cat
|
||||
{
|
||||
"Account": {
|
||||
"Email": "$mail",
|
||||
"Registration": {
|
||||
"body": {
|
||||
"status": "valid",
|
||||
"contact": [
|
||||
"mailto:$mail"
|
||||
]
|
||||
},
|
||||
"uri": "https://acme-v02.api.letsencrypt.org/acme/acct/$RANDOM"
|
||||
},
|
||||
"PrivateKey": "$(acme_strip_tag $keyfile)",
|
||||
"KeyType": "2048"
|
||||
},
|
||||
"Certificates": [
|
||||
{
|
||||
"Domain": {
|
||||
"Main": "$host",
|
||||
"SANs": null
|
||||
},
|
||||
"Certificate": "$(base64 -w 0 $certfile)",
|
||||
"Key": "$(base64 -w 0 $keyfile)"
|
||||
}
|
||||
],
|
||||
"HTTPChallenges": {},
|
||||
"TLSChallenges": {}
|
||||
}
|
||||
!cat
|
|
@ -0,0 +1,34 @@
|
|||
# dkr.mk
|
||||
#
|
||||
# Container make-functions
|
||||
#
|
||||
|
||||
#
|
||||
# $(call dkr_srv_cnt,app) -> d03dda046e0b90c...
|
||||
#
|
||||
dkr_srv_cnt = $(shell docker-compose ps -q $(1) | head -n1)
|
||||
#
|
||||
# $(call dkr_cnt_ip,demo_app_1) -> 172.28.0.3
|
||||
#
|
||||
dkr_cnt_ip = $(shell docker inspect -f \
|
||||
'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
|
||||
$(1) | head -n1)
|
||||
#
|
||||
# $(call dkr_srv_ip,app) -> 172.28.0.3
|
||||
#
|
||||
dkr_srv_ip = $(shell docker inspect -f \
|
||||
'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
|
||||
$$(docker-compose ps -q $(1)) | head -n1)
|
||||
#
|
||||
#cnt_ip_old = $(shell docker inspect -f \
|
||||
# '{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' \
|
||||
# $(1) | head -n1)
|
||||
|
||||
#
|
||||
# List IPs of containers
|
||||
#
|
||||
ip-list:
|
||||
@for srv in $$(docker ps --format "{{.Names}}"); do \
|
||||
echo $$srv $$(docker inspect -f \
|
||||
'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $$srv); \
|
||||
done | column -t
|
|
@ -0,0 +1,125 @@
|
|||
# ssl.mk
|
||||
#
|
||||
# SSL and TLS make-functions
|
||||
#
|
||||
|
||||
SSL_O ?= example.com
|
||||
SSL_KEY ?= rsa:2048 # rsa:2048 rsa:4096
|
||||
SSL_MAIL ?=
|
||||
SSL_PASS ?= secret
|
||||
SSL_SAN ?=
|
||||
SSL_TRST ?=
|
||||
|
||||
#
|
||||
# Usage: OpenLDAP
|
||||
#
|
||||
#SSL_O = $(AD_DOM)
|
||||
#target: ssl/auth.crt ssl/demo.crt
|
||||
|
||||
#
|
||||
# Usage: SMIME
|
||||
#
|
||||
#SSL_O = $(MAIL_DOMAIN)
|
||||
#SSL_MAIL = auto
|
||||
#SSL_PASS = $(AD_USR_PW)
|
||||
##SSL_TRST = $(SSL_SMIME)
|
||||
#target: ssl/$(AD_USR_CN)@$(MAIL_DOMAIN).p12
|
||||
SSL_SMIME = -setalias "Self Signed SMIME" -addtrust emailProtection \
|
||||
-addreject clientAuth -addreject serverAuth
|
||||
|
||||
#
|
||||
# Usage: SUbject Alternate Name SAN
|
||||
#
|
||||
#SSL_O = example.com
|
||||
#SSL_SAN = "subjectAltName=DNS:auth,DNS:*.docker"
|
||||
#target: ssl/auth.crt
|
||||
|
||||
|
||||
#
|
||||
# $(call ssl_subj,root,example.com,) -> -subj "/CN=root/O=example.com"
|
||||
# $(call ssl_subj,root,example.com,auto) -> -subj "/CN=root/O=example.com/emailAddress=root@example.com"
|
||||
# $(call ssl_subj,root,example.com,admin@my.org) -> -subj "/CN=root/O=example.com/emailAddress=admin@my.org"
|
||||
#
|
||||
ssl_subj = -subj "/CN=$(1)/O=$(2)$(if $(3),/emailAddress=$(if $(findstring @,$(3)),$(3),$(1)@$(2)),)"
|
||||
|
||||
#
|
||||
# $(call ssl_extfile,"subjectAltName=DNS:auth") -> -extfile <(printf "subjectAltName=DNS:auth")
|
||||
#
|
||||
ssl_extfile = $(if $(1),-extfile <(printf $(1)),)
|
||||
|
||||
|
||||
.PRECIOUS: %.crt %.csr %.key
|
||||
SHELL = /bin/bash
|
||||
|
||||
#
|
||||
# Personal information exchange file PKCS#12
|
||||
#
|
||||
%.p12: %.crt
|
||||
openssl pkcs12 -export -in $< -inkey $*.key -out $@ \
|
||||
-passout pass:$(SSL_PASS)
|
||||
|
||||
#
|
||||
# Certificate PEM
|
||||
#
|
||||
%.crt: %.csr ssl/ca.crt
|
||||
openssl x509 -req -in $< -CA $(@D)/ca.crt -CAkey $(@D)/ca.key -out $@ \
|
||||
$(call ssl_extfile,$(SSL_SAN)) $(SSL_TRST) -CAcreateserial
|
||||
|
||||
#
|
||||
# Certificate signing request PEM
|
||||
#
|
||||
%.csr: ssl
|
||||
openssl req -new -newkey $(SSL_KEY) -nodes -keyout $*.key -out $@ \
|
||||
$(call ssl_subj,$(*F),$(SSL_O),$(SSL_MAIL))
|
||||
|
||||
#
|
||||
# Certificate authority certificate PEM
|
||||
#
|
||||
ssl/ca.crt: ssl
|
||||
openssl req -x509 -new -newkey $(SSL_KEY) -nodes -keyout ssl/ca.key -out $@ \
|
||||
$(call ssl_subj,root,$(SSL_O),$(SSL_MAIL))
|
||||
|
||||
#
|
||||
# SSL directory
|
||||
#
|
||||
ssl:
|
||||
mkdir -p $@
|
||||
|
||||
#
|
||||
# Remove all files in SSL directory
|
||||
#
|
||||
ssl-destroy:
|
||||
rm -f ssl/*
|
||||
|
||||
#
|
||||
# Inspect all files in SSL directory
|
||||
#
|
||||
ssl-list:
|
||||
@for file in $$(ls ssl/*); do \
|
||||
case $$file in \
|
||||
*.crt) \
|
||||
printf "\e[33;1m%s\e[0m\n" $$file; \
|
||||
openssl x509 -noout -issuer -subject -ext basicConstraints,keyUsage,extendedKeyUsage,subjectAltName -in $$file;; \
|
||||
*.csr) \
|
||||
printf "\e[33;1m%s\e[0m\n" $$file; \
|
||||
openssl req -noout -subject -in $$file;; \
|
||||
*.key) \
|
||||
printf "\e[33;1m%s\e[0m\n" $$file; \
|
||||
openssl rsa -text -noout -in $$file | head -n 1;; \
|
||||
esac \
|
||||
done
|
||||
|
||||
ssl-inspect:
|
||||
@for file in $$(ls ssl/*); do \
|
||||
case $$file in \
|
||||
*.crt) \
|
||||
printf "\e[33;1m%s\e[0m " $$file; \
|
||||
openssl x509 -text -noout -certopt no_sigdump,no_pubkey -in $$file;; \
|
||||
*.csr) \
|
||||
printf "\e[33;1m%s\e[0m " $$file; \
|
||||
openssl req -text -noout -reqopt no_sigdump,no_pubkey,ext_default -in $$file;; \
|
||||
*.key) \
|
||||
printf "\e[33;1m%s\e[0m " $$file; \
|
||||
openssl rsa -text -noout -in $$file | head -n 1;; \
|
||||
esac \
|
||||
done
|
|
@ -0,0 +1,4 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
Loading…
Reference in New Issue