diff --git a/bin/quickstart.sh b/bin/quickstart.sh index 723da199..4dd80d2b 100755 --- a/bin/quickstart.sh +++ b/bin/quickstart.sh @@ -1,143 +1,143 @@ #!/bin/bash set -e function die() { echo "$1" exit 1 } rpm -qv docker-compose >/dev/null 2>&1 || \ test ! -z "$(which docker-compose 2>/dev/null)" || \ die "Is docker-compose installed?" test ! -z "$(grep 'systemd.unified_cgroup_hierarchy=0' /proc/cmdline)" || \ die "systemd containers only work with cgroupv1 (use 'grubby --update-kernel=ALL --args=\"systemd.unified_cgroup_hierarchy=0\"' and a reboot to fix)" base_dir=$(dirname $(dirname $0)) # Always reset .env with .env.example cp src/.env.example src/.env if [ -f "src/env.local" ]; then # Ensure there's a line ending echo "" >> src/.env cat src/env.local >> src/.env fi export DOCKER_BUILDKIT=0 docker pull docker.io/kolab/centos7:latest docker-compose down --remove-orphans src/artisan octane:stop >/dev/null 2>&1 || : src/artisan horizon:terminate >/dev/null 2>&1 || : -docker-compose build coturn kolab mariadb meet pdns-sql proxy redis nginx +docker-compose build coturn kolab mariadb meet pdns-sql proxy redis nginx haproxy bin/regen-certs -docker-compose up -d coturn kolab mariadb meet pdns-sql proxy redis +docker-compose up -d coturn kolab mariadb meet pdns-sql proxy redis haproxy # Workaround until we have docker-compose --wait (https://github.com/docker/compose/pull/8777) function wait_for_container { container_id="$1" container_name="$(docker inspect "${container_id}" --format '{{ .Name }}')" echo "Waiting for container: ${container_name} [${container_id}]" waiting_done="false" while [[ "${waiting_done}" != "true" ]]; do container_state="$(docker inspect "${container_id}" --format '{{ .State.Status }}')" if [[ "${container_state}" == "running" ]]; then health_status="$(docker inspect "${container_id}" --format '{{ .State.Health.Status }}')" echo "${container_name}: container_state=${container_state}, health_status=${health_status}" if [[ ${health_status} == "healthy" ]]; then waiting_done="true" fi else echo "${container_name}: container_state=${container_state}" waiting_done="true" fi sleep 1; done; } # Ensure the containers we depend on are fully started wait_for_container 'kolab' wait_for_container 'kolab-redis' if [ "$1" == "--nodev" ]; then echo "starting everything in containers" docker-compose build swoole docker-compose build webapp docker-compose up -d webapp nginx wait_for_container 'kolab-webapp' exit 0 fi echo "Starting the development environment" rpm -qv composer >/dev/null 2>&1 || \ test ! -z "$(which composer 2>/dev/null)" || \ die "Is composer installed?" rpm -qv npm >/dev/null 2>&1 || \ test ! -z "$(which npm 2>/dev/null)" || \ die "Is npm installed?" rpm -qv php >/dev/null 2>&1 || \ test ! -z "$(which php 2>/dev/null)" || \ die "Is php installed?" rpm -qv php-ldap >/dev/null 2>&1 || \ test ! -z "$(php --ini | grep ldap)" || \ die "Is php-ldap installed?" rpm -qv php-mysqlnd >/dev/null 2>&1 || \ test ! -z "$(php --ini | grep mysql)" || \ die "Is php-mysqlnd installed?" test ! -z "$(php --modules | grep swoole)" || \ die "Is swoole installed?" pushd ${base_dir}/src/ rm -rf vendor/ composer.lock php -dmemory_limit=-1 $(which composer) install npm install find bootstrap/cache/ -type f ! -name ".gitignore" -delete ./artisan key:generate ./artisan clear-compiled ./artisan cache:clear ./artisan horizon:install if [ ! -f storage/oauth-public.key -o ! -f storage/oauth-private.key ]; then ./artisan passport:keys --force fi cat >> .env << EOF PASSPORT_PRIVATE_KEY="$(cat storage/oauth-private.key)" PASSPORT_PUBLIC_KEY="$(cat storage/oauth-public.key)" EOF if rpm -qv chromium 2>/dev/null; then chver=$(rpmquery --queryformat="%{VERSION}" chromium | awk -F'.' '{print $1}') ./artisan dusk:chrome-driver ${chver} fi if [ ! -f 'resources/countries.php' ]; then ./artisan data:countries fi npm run dev popd docker-compose up -d nginx pushd ${base_dir}/src/ rm -rf database/database.sqlite ./artisan db:ping --wait php -dmemory_limit=512M ./artisan migrate:refresh --seed ./artisan data:import || : nohup ./artisan octane:start --host=$(grep OCTANE_HTTP_HOST .env | tail -n1 | sed "s/OCTANE_HTTP_HOST=//") > octane.out & nohup ./artisan horizon > horizon.out & popd diff --git a/docker-compose.yml b/docker-compose.yml index 97da6524..dd7dc264 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,254 +1,275 @@ version: '3' services: coturn: build: context: ./docker/coturn/ container_name: kolab-coturn healthcheck: interval: 10s test: "kill -0 $$(cat /tmp/turnserver.pid)" timeout: 5s retries: 30 environment: - TURN_PUBLIC_IP=${COTURN_PUBLIC_IP} - TURN_LISTEN_PORT=3478 - TURN_STATIC_SECRET=${COTURN_STATIC_SECRET} hostname: sturn.mgmt.com image: kolab-coturn network_mode: host restart: on-failure tty: true kolab: build: context: ./docker/kolab/ container_name: kolab privileged: true depends_on: mariadb: condition: service_healthy extra_hosts: - "kolab.mgmt.com:127.0.0.1" environment: - DB_HOST=${DB_HOST} - DB_ROOT_PASSWORD=Welcome2KolabSystems - DB_HKCCP_DATABASE=${DB_DATABASE} - DB_HKCCP_USERNAME=${DB_USERNAME} - DB_HKCCP_PASSWORD=${DB_PASSWORD} - DB_KOLAB_DATABASE=kolab - DB_KOLAB_USERNAME=kolab - DB_KOLAB_PASSWORD=Welcome2KolabSystems - DB_RC_USERNAME=roundcube - DB_RC_PASSWORD=Welcome2KolabSystems - SSL_CERTIFICATE=${KOLAB_SSL_CERTIFICATE:?err} - SSL_CERTIFICATE_FULLCHAIN=${KOLAB_SSL_CERTIFICATE_FULLCHAIN:?err} - SSL_CERTIFICATE_KEY=${KOLAB_SSL_CERTIFICATE_KEY:?err} - IMAP_HOST=127.0.0.1 - IMAP_PORT=11993 - MAIL_HOST=127.0.0.1 - MAIL_PORT=10587 healthcheck: interval: 10s test: test -f /tmp/kolab-init.done timeout: 5s retries: 30 hostname: kolab.mgmt.com image: kolab network_mode: host tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - ./ext/:/src/:ro - /etc/letsencrypt/:/etc/letsencrypt/:ro - ./docker/certs/ca.cert:/etc/pki/tls/certs/ca.cert:ro - ./docker/certs/ca.cert:/etc/pki/ca-trust/source/anchors/ca.cert:ro - ./docker/certs/kolab.hosted.com.cert:/etc/pki/tls/certs/kolab.hosted.com.cert - ./docker/certs/kolab.hosted.com.chain.pem:/etc/pki/tls/certs/kolab.hosted.com.chain.pem - ./docker/certs/kolab.hosted.com.key:/etc/pki/tls/certs/kolab.hosted.com.key - ./docker/certs/kolab.mgmt.com.cert:/etc/pki/tls/certs/kolab.mgmt.com.cert - ./docker/certs/kolab.mgmt.com.key:/etc/pki/tls/certs/kolab.mgmt.com.key - ./docker/kolab/utils:/root/utils:ro - ./src/.env:/.dockerenv:ro - /sys/fs/cgroup:/sys/fs/cgroup:ro mariadb: container_name: kolab-mariadb environment: MYSQL_ROOT_PASSWORD: Welcome2KolabSystems TZ: "+02:00" healthcheck: interval: 10s test: test -e /var/run/mysqld/mysqld.sock timeout: 5s retries: 30 image: mariadb network_mode: host nginx: build: context: ./docker/nginx/ args: APP_WEBSITE_DOMAIN: ${APP_WEBSITE_DOMAIN:?err} SSL_CERTIFICATE: ${NGINX_SSL_CERTIFICATE:?err} SSL_CERTIFICATE_KEY: ${NGINX_SSL_CERTIFICATE_KEY:?err} depends_on: kolab: condition: service_healthy healthcheck: interval: 10s test: "kill -0 $$(cat /run/nginx.pid)" timeout: 5s retries: 30 container_name: kolab-nginx hostname: nginx.hosted.com image: kolab-nginx network_mode: host tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - ./docker/certs/:/etc/certs/:ro - /etc/letsencrypt/:/etc/letsencrypt/:ro + haproxy: + build: + context: ./docker/haproxy/ + healthcheck: + interval: 10s + test: "kill -0 $$(cat /var/run/haproxy.pid)" + timeout: 5s + retries: 30 + container_name: kolab-haproxy + hostname: haproxy.hosted.com + image: kolab-haproxy + network_mode: host + tmpfs: + - /run + - /tmp + - /var/run + - /var/tmp + tty: true + volumes: + - ./docker/certs/:/etc/certs/:ro + - /etc/letsencrypt/:/etc/letsencrypt/:ro pdns-sql: build: context: ./docker/pdns-sql/ container_name: kolab-pdns-sql depends_on: mariadb: condition: service_healthy healthcheck: interval: 10s test: "systemctl status pdns || exit 1" timeout: 5s retries: 30 hostname: pdns-sql image: apheleia/kolab-pdns-sql network_mode: host tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - /sys/fs/cgroup:/sys/fs/cgroup:ro proxy: build: context: ./docker/proxy/ args: APP_WEBSITE_DOMAIN: ${APP_WEBSITE_DOMAIN:?err} SSL_CERTIFICATE: ${PROXY_SSL_CERTIFICATE:?err} SSL_CERTIFICATE_KEY: ${PROXY_SSL_CERTIFICATE_KEY:?err} healthcheck: interval: 10s test: "kill -0 $$(cat /run/nginx.pid)" timeout: 5s retries: 30 container_name: kolab-proxy hostname: ${APP_WEBSITE_DOMAIN:?err} image: kolab-proxy network_mode: host tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - ./docker/certs/:/etc/certs/:ro - /etc/letsencrypt/:/etc/letsencrypt/:ro redis: build: context: ./docker/redis/ healthcheck: interval: 10s test: "redis-cli ping || exit 1" timeout: 5s retries: 30 container_name: kolab-redis hostname: redis image: redis network_mode: host volumes: - ./docker/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro swoole: build: context: ./docker/swoole/ container_name: kolab-swoole image: apheleia/swoole:4.8.x webapp: build: context: ./docker/webapp/ container_name: kolab-webapp image: kolab-webapp healthcheck: interval: 10s test: "/src/kolabsrc/artisan octane:status || exit 1" timeout: 5s retries: 30 depends_on: kolab: condition: service_healthy network_mode: host volumes: - ./src:/src/kolabsrc.orig:ro tests: build: context: ./docker/tests/ container_name: kolab-tests image: kolab-tests depends_on: kolab: condition: service_healthy network_mode: host volumes: - ./src:/src/kolabsrc.orig:ro worker: build: context: ./docker/worker/ container_name: kolab-worker depends_on: - kolab hostname: worker image: kolab-worker network_mode: host tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - ./src:/home/worker/src.orig:ro - /sys/fs/cgroup:/sys/fs/cgroup:ro meet: build: context: ./docker/meet/ healthcheck: interval: 10s test: "curl --insecure -H 'X-AUTH-TOKEN: ${MEET_SERVER_TOKEN}' --fail https://localhost:12443/meetmedia/api/health || exit 1" timeout: 5s retries: 30 environment: - WEBRTC_LISTEN_IP=${MEET_WEBRTC_LISTEN_IP:?err} - PUBLIC_DOMAIN=${MEET_PUBLIC_DOMAIN:?err} - LISTENING_HOST=0.0.0.0 - LISTENING_PORT=12443 - TURN_SERVER=${MEET_TURN_SERVER} - TURN_STATIC_SECRET=${COTURN_STATIC_SECRET} - AUTH_TOKEN=${MEET_SERVER_TOKEN:?err} - WEBHOOK_TOKEN=${MEET_WEBHOOK_TOKEN:?err} - WEBHOOK_URL=${APP_PUBLIC_URL:?err}/api/webhooks/meet - SSL_CERT=/etc/pki/tls/certs/meet.${APP_WEBSITE_DOMAIN:?err}.cert - SSL_KEY=/etc/pki/tls/private/meet.${APP_WEBSITE_DOMAIN:?err}.key network_mode: host container_name: kolab-meet image: kolab-meet volumes: - ./meet/server:/src/meet/:ro - ./docker/certs/meet.${APP_WEBSITE_DOMAIN}.cert:/etc/pki/tls/certs/meet.${APP_WEBSITE_DOMAIN}.cert - ./docker/certs/meet.${APP_WEBSITE_DOMAIN}.key:/etc/pki/tls/private/meet.${APP_WEBSITE_DOMAIN}.key diff --git a/docker/haproxy/Dockerfile b/docker/haproxy/Dockerfile new file mode 100644 index 00000000..0c6b0196 --- /dev/null +++ b/docker/haproxy/Dockerfile @@ -0,0 +1,18 @@ +FROM fedora:35 + +MAINTAINER Jeroen van Meeuwen + +ENV container docker + +RUN dnf -y install \ + --setopt 'tsflags=nodocs' \ + haproxy && \ + dnf clean all + +COPY haproxy.cfg /etc/haproxy/haproxy.cfg + +STOPSIGNAL SIGUSR1 + +CMD ["haproxy", "-d", "-db", "-W", "-f", "/etc/haproxy/haproxy.cfg"] + +#EXPOSE 110/tcp 143/tcp 993/tcp 995/tcp diff --git a/docker/haproxy/haproxy.cfg b/docker/haproxy/haproxy.cfg new file mode 100644 index 00000000..766dcb38 --- /dev/null +++ b/docker/haproxy/haproxy.cfg @@ -0,0 +1,76 @@ +#--------------------------------------------------------------------- +# Example configuration for a possible web application. See the +# full configuration options online. +# +# https://www.haproxy.org/download/1.8/doc/configuration.txt +# +#--------------------------------------------------------------------- + +#--------------------------------------------------------------------- +# Global settings +#--------------------------------------------------------------------- +global + # to have these messages end up in /var/log/haproxy.log you will + # need to: + # + # 1) configure syslog to accept network log events. This is done + # by adding the '-r' option to the SYSLOGD_OPTIONS in + # /etc/sysconfig/syslog + # + # 2) configure local2 events to go to the /var/log/haproxy.log + # file. A line like the following can be added to + # /etc/sysconfig/syslog + # + # local2.* /var/log/haproxy.log + # + log 127.0.0.1 local2 + + chroot /var/lib/haproxy + pidfile /var/run/haproxy.pid + maxconn 100 + user haproxy + group haproxy + daemon + + # turn on stats unix socket + stats socket /var/lib/haproxy/stats + + # utilize system-wide crypto-policies + ssl-default-bind-ciphers PROFILE=SYSTEM + ssl-default-server-ciphers PROFILE=SYSTEM + +#--------------------------------------------------------------------- +# common defaults that all the 'listen' and 'backend' sections will +# use if not designated in their block +#--------------------------------------------------------------------- +defaults +# mode http + log global +# option httplog +# option dontlognull +# option http-server-close +# option forwardfor except 127.0.0.0/8 +# option redispatch +# retries 3 +# timeout http-request 10s + timeout queue 1m + timeout connect 10s + timeout client 1m + timeout server 1m +# timeout http-keep-alive 10s + timeout check 10s + maxconn 3000 + +frontend ft_imap + # Accept the proxy protocol + bind :145 accept-proxy + mode tcp + default_backend bk_imap +backend bk_imap + mode tcp + # imap connections are usually long + balance leastconn + stick store-request src + stick-table type ip size 200k expire 30m + # NGINX imap with proxy protocol enabled + server s1 127.0.0.1:144 check send-proxy-v2 diff --git a/docker/kolab/utils/24-roundcubeconfig.sh b/docker/kolab/utils/24-roundcubeconfig.sh index 8d9e889a..ce190232 100755 --- a/docker/kolab/utils/24-roundcubeconfig.sh +++ b/docker/kolab/utils/24-roundcubeconfig.sh @@ -1,42 +1,42 @@ #!/bin/bash sed -i -r -e "s|$config\['kolab_files_url'\] = .*$|$config['kolab_files_url'] = 'https://' \. \$_SERVER['HTTP_HOST'] . '/chwala/';|g" /etc/roundcubemail/kolab_files.inc.php sed -i -r -e "s|$config\['kolab_invitation_calendars'\] = .*$|$config['kolab_invitation_calendars'] = true;|g" /etc/roundcubemail/calendar.inc.php sed -i -r -e "/^.*'contextmenu',$/a 'enigma'," /etc/roundcubemail/config.inc.php sed -i -r -e "s|$config\['enigma_passwordless'\] = .*$|$config['enigma_passwordless'] = true;|g" /etc/roundcubemail/enigma.inc.php sed -i -r -e "s|$config\['enigma_multihost'\] = .*$|$config['enigma_multihost'] = true;|g" /etc/roundcubemail/enigma.inc.php echo "\$config['enigma_woat'] = true;" >> /etc/roundcubemail/enigma.inc.php -# Run it over nginx for 2fa. We need to use startls because otherwise the proxy protocol doesn't work. +# Run it over haproxy then nginx for 2fa. We need to use startls because otherwise the proxy protocol doesn't work. sed -i -r -e "s|$config\['default_host'\] = .*$|$config['default_host'] = 'tls://127.0.0.1';|g" /etc/roundcubemail/config.inc.php -sed -i -r -e "s|$config\['default_port'\] = .*$|$config['default_port'] = 144;|g" /etc/roundcubemail/config.inc.php +sed -i -r -e "s|$config\['default_port'\] = .*$|$config['default_port'] = 145;|g" /etc/roundcubemail/config.inc.php # So we can just append sed -i "s/?>//g" /etc/roundcubemail/config.inc.php # Enable the PROXY protocol cat << EOF >> /etc/roundcubemail/config.inc.php \$config['imap_conn_options'] = Array( 'ssl' => Array( 'verify_peer_name' => false, 'verify_peer' => false, 'allow_self_signed' => true ), 'proxy_protocol' => 2 ); \$config['proxy_whitelist'] = array('127.0.0.1'); EOF echo "?>" >> /etc/roundcubemail/config.inc.php # Send dns queries over powerdns echo "server=/_woat.kolab.org/127.0.0.1#9953" >> /etc/dnsmasq.conf echo "port=5353" >> /etc/dnsmasq.conf systemctl start dnsmasq rm -f /etc/resolv.conf echo "nameserver 127.0.0.1:5353" > /etc/resolv.conf