diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4f9cefda0776745d43e853a665614dfb02c4c45e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.iml +/.idea/* +!/.idea/copyright/ +.DS_Store diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..de3af6cf386723523401061601451beb3ec52943 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,9 @@ +build: + stage: build + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + script: + - mkdir -p /kaniko/.docker + - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA} --destination $CI_REGISTRY_IMAGE:latest diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..9a3daca45cd91a628cf5d31eaaeb55ae89a63956 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,73 @@ +FROM alpine:3.15 +RUN apk add --no-cache \ + nginx \ + python3 \ + py3-pip \ + curl \ + unzip \ + php7 \ + php7-fpm \ + php7-mbstring \ + php7-json \ + php7-xml \ + php7-dom \ + php7-curl \ + php7-exif \ + php7-gd \ + php7-iconv \ + php7-intl \ + php7-openssl \ + php7-pdo \ + php7-pdo_sqlite \ + php7-pdo_pgsql \ + php7-pecl-redis \ + php7-sodium \ + php7-tidy \ + php7-pecl-uuid \ + php7-zip \ + && rm /etc/nginx/http.d/default.conf \ + && rm /etc/php7/php-fpm.d/www.conf \ + && mkdir -p /run/nginx \ + && mkdir -p /var/www/snappymail \ + && mkdir -p /config \ + && pip3 install socrate==0.2.0 + +# nginx / PHP config files +COPY config/nginx-snappymail.conf /config/nginx-snappymail.conf +COPY config/php-snappymail.conf /etc/php7/php-fpm.d/snappymail.conf + +# Snappymail login +COPY login/include.php /var/www/snappymail/include.php +COPY login/sso.php /var/www/snappymail/sso.php + +# Parsing configs moved to startup +COPY defaults/php.ini /defaults/php.ini +COPY defaults/application.ini /defaults/application.ini +COPY defaults/default.ini /defaults/default.ini + +ENV APP_VERSION 2.15.1 +ENV SNAPPYMAIL_URL https://github.com/the-djmaze/snappymail/releases/download/v$APP_VERSION/snappymail-$APP_VERSION.zip + +# Install Snappymail from source + +RUN apk add --no-cache \ + curl unzip \ + && cd /var/www/snappymail \ + && curl -L -O ${SNAPPYMAIL_URL} \ + && unzip -q *.zip \ + && rm -f *.zip \ + && rm -rf data/ \ + && find . -type d -exec chmod 755 {} \; \ + && find . -type f -exec chmod 644 {} \; \ + && chown -R nginx:nginx /var/www/snappymail \ + && apk del unzip + +COPY start.py /start.py +COPY config.py /config.py + +EXPOSE 80/tcp +VOLUME ["/data"] + +ENTRYPOINT ["/start.py"] + +HEALTHCHECK CMD curl -f -L http://localhost/ || exit 1 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..1949a53a90813eeb4dd4499fcd0ca9d00aa2a68c --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +IMAGE := k8r.eu/justjanne/$(shell basename $(shell git remote get-url origin) .git) +TAGS := $(shell git describe --always --tags HEAD) + +.PHONY: build +build: + docker build --pull -t $(IMAGE):$(TAGS) . + docker tag $(IMAGE):$(TAGS) $(IMAGE):latest + @echo Successfully tagged $(IMAGE):$(TAGS) as latest + +.PHONY: push +push: build + docker push $(IMAGE):$(TAGS) + docker push $(IMAGE):latest + @echo Successfully pushed $(IMAGE):$(TAGS) as latest diff --git a/config.py b/config.py new file mode 100755 index 0000000000000000000000000000000000000000..ec6f51510af3778b98e4a88f95d383c4dc79876c --- /dev/null +++ b/config.py @@ -0,0 +1,15 @@ +#!/usr/bin/python3 + +import os +import logging as log +import sys +from socrate import system, conf + +args = os.environ.copy() + +log.basicConfig(stream=sys.stderr, level=args.get("LOG_LEVEL", "WARNING")) + +# Build final configuration paths +conf.jinja("/config/nginx-snappymail.conf", args, "/etc/nginx/http.d/snappymail.conf") +if os.path.exists("/var/run/nginx.pid"): + os.system("nginx -s reload") diff --git a/config/nginx-snappymail.conf b/config/nginx-snappymail.conf new file mode 100644 index 0000000000000000000000000000000000000000..374a65794fbc0d026e6c79fd5fac10226e4cfa23 --- /dev/null +++ b/config/nginx-snappymail.conf @@ -0,0 +1,46 @@ +server { + listen 80 default_server; + listen [::]:80 default_server; + + root /var/www/snappymail; + + # /dev/stdout (Default), <path>, off + access_log off; + + # /dev/stderr (Default), <path>, debug, info, notice, warn, error, crit, alert, emerg + error_log /dev/stderr warn; + + index index.php; + + # set maximum body size to configured limit + client_max_body_size {{ MESSAGE_SIZE_LIMIT|int + 8388608 }}; + + location /healthz { + access_log off; + return 200 "OK\n"; + } + + location / { + try_files $uri /index.php?$query_string; + } + + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.*)$; + + fastcgi_intercept_errors on; + fastcgi_index index.php; + + fastcgi_keep_conn on; + include /etc/nginx/fastcgi_params; + fastcgi_pass unix:/var/run/php7-fpm.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + } + + location ~ /\.ht { + deny all; + } + + location ^~ /data { + deny all; + } +} diff --git a/config/php-snappymail.conf b/config/php-snappymail.conf new file mode 100644 index 0000000000000000000000000000000000000000..d60104007c6dbc8cbbfed6cef3d37f67a517b24c --- /dev/null +++ b/config/php-snappymail.conf @@ -0,0 +1,101 @@ +; Start a new pool named 'snappymail'. +; the variable $pool can be used in any directive and will be replaced by the +; pool name ('snappymail' here) +[snappymail] + +; Redirect worker stdout and stderr into main error log. If not set, stdout and +; stderr will be redirected to /dev/null according to FastCGI specs. +; Default value: no. +catch_workers_output = 1 + +; Unix user/group of processes +; Note: The user is mandatory. If the group is not set, the default user's group +; will be used. +user = nginx +group = nginx + +; The address on which to accept FastCGI requests. +; Valid syntaxes are: +; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on +; a specific port; +; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on +; a specific port; +; 'port' - to listen on a TCP socket to all addresses +; (IPv6 and IPv4-mapped) on a specific port; +; '/path/to/unix/socket' - to listen on a unix socket. +; Note: This value is mandatory. +listen = /var/run/php7-fpm.sock + +; Set permissions for unix socket, if one is used. In Linux, read/write +; permissions must be set in order to allow connections from a web server. Many +; BSD-derived systems allow connections regardless of permissions. +; Default Values: user and group are set as the running user +; mode is set to 0660 +listen.owner = nginx +listen.group = nginx +listen.mode = 0660 + +; Choose how the process manager will control the number of child processes. +; Possible Values: +; static - a fixed number (pm.max_children) of child processes; +; dynamic - the number of child processes are set dynamically based on the +; following directives. With this process management, there will be +; always at least 1 children. +; pm.max_children - the maximum number of children that can +; be alive at the same time. +; pm.start_servers - the number of children created on startup. +; pm.min_spare_servers - the minimum number of children in 'idle' +; state (waiting to process). If the number +; of 'idle' processes is less than this +; number then some children will be created. +; pm.max_spare_servers - the maximum number of children in 'idle' +; state (waiting to process). If the number +; of 'idle' processes is greater than this +; number then some children will be killed. +; ondemand - no children are created at startup. Children will be forked when +; new requests will connect. The following parameter are used: +; pm.max_children - the maximum number of children that +; can be alive at the same time. +; pm.process_idle_timeout - The number of seconds after which +; an idle process will be killed. +; Note: This value is mandatory. +pm = ondemand + +; The number of child processes to be created when pm is set to 'static' and the +; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'. +; This value sets the limit on the number of simultaneous requests that will be +; served. Equivalent to the ApacheMaxClients directive with mpm_prefork. +; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP +; CGI. The below defaults are based on a server without much resources. Don't +; forget to tweak pm.* to fit your needs. +; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand' +; Note: This value is mandatory. +pm.max_children = 5 + +; The number of child processes created on startup. +; Note: Used only when pm is set to 'dynamic' +; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2 +; pm.start_servers = 2 + +; The desired minimum number of idle server processes. +; Note: Used only when pm is set to 'dynamic' +; Note: Mandatory when pm is set to 'dynamic' +; pm.min_spare_servers = 1 + +; The desired maximum number of idle server processes. +; Note: Used only when pm is set to 'dynamic' +; Note: Mandatory when pm is set to 'dynamic' +; pm.max_spare_servers = 3 + +; This sets the maximum time in seconds a script is allowed to run before it is +; terminated by the parser. This helps prevent poorly written scripts from tying up +; the server. The default setting is 30s. +; Note: Used only when pm is set to 'ondemand' +pm.process_idle_timeout = 10s + +; The number of requests each child process should execute before respawning. +; This can be useful to work around memory leaks in 3rd party libraries. For endless +; request processing specify '0'. +; Equivalent to PHP_FCGI_MAX_REQUESTS. Default value: 0. +; Noted: Used only when pm is set to 'ondemand' +pm.max_requests = 200 diff --git a/defaults/application.ini b/defaults/application.ini new file mode 100644 index 0000000000000000000000000000000000000000..bdcd86e2490965f9bd76df4c321d0d62b24ebddf --- /dev/null +++ b/defaults/application.ini @@ -0,0 +1,19 @@ +; snappymail Webmail configuration file + +[webmail] +attachment_size_limit = {{ MAX_FILESIZE }} + +[security] +allow_admin_panel = Off + +[labs] +allow_gravatar = Off +custom_login_link='sso.php' +custom_logout_link='/sso/logout' + +[contacts] +enable = On +allow_sync = On + +[defaults] +contacts_autosave = On diff --git a/defaults/default.ini b/defaults/default.ini new file mode 100644 index 0000000000000000000000000000000000000000..be9a0969244aabe360f4dfbfbf086584eb413e43 --- /dev/null +++ b/defaults/default.ini @@ -0,0 +1,15 @@ +imap_host = "{{ FRONT_ADDRESS }}" +imap_port = 10143 +imap_secure = "None" +imap_short_login = Off +sieve_use = On +sieve_allow_raw = Off +sieve_host = "{{ IMAP_ADDRESS }}" +sieve_port = 4190 +sieve_secure = "None" +smtp_host = "{{ FRONT_ADDRESS }}" +smtp_port = 10025 +smtp_secure = "None" +smtp_short_login = Off +smtp_auth = On +smtp_php_mail = Off diff --git a/defaults/php.ini b/defaults/php.ini new file mode 100644 index 0000000000000000000000000000000000000000..012e4af2810779982570f0b9d09875799724951b --- /dev/null +++ b/defaults/php.ini @@ -0,0 +1,4 @@ +expose_php=Off +date.timezone=UTC +upload_max_filesize = {{ MAX_FILESIZE }}M +post_max_size = {{ MAX_FILESIZE }}M diff --git a/login/include.php b/login/include.php new file mode 100644 index 0000000000000000000000000000000000000000..fde913247109583b106287907d8b0318c04d8c05 --- /dev/null +++ b/login/include.php @@ -0,0 +1,19 @@ +<?php + +// Rename this file to "include.php" to enable it. + +/** + * @return string + */ +function __get_custom_data_full_path() +{ + return '/data/'; // custom data folder path +} + +/** + * @return string + */ +function __get_additional_configuration_name() +{ + return 'application.ini'; +} diff --git a/login/sso.php b/login/sso.php new file mode 100644 index 0000000000000000000000000000000000000000..ace5ce1844f9720eb3857ad25873d0fffc0b04bb --- /dev/null +++ b/login/sso.php @@ -0,0 +1,16 @@ +<?php + +$_ENV['SNAPPYMAIL_INCLUDE_AS_API'] = true; +include "index.php"; + +// Retrieve email and password +if (isset($_SERVER['HTTP_X_REMOTE_USER']) && isset($_SERVER['HTTP_X_REMOTE_USER_TOKEN'])) { + $email = $_SERVER['HTTP_X_REMOTE_USER']; + $password = $_SERVER['HTTP_X_REMOTE_USER_TOKEN']; + $ssoHash = \RainLoop\Api::CreateUserSsoHash($email, $password); + + // redirect to webmail sso url + header('Location: /?sso&hash='.$ssoHash); +} else { + header('HTTP/1.0 403 Forbidden'); +} diff --git a/start.py b/start.py new file mode 100755 index 0000000000000000000000000000000000000000..af1ef34cdad4ceefe993aee6ee777c7311f86d24 --- /dev/null +++ b/start.py @@ -0,0 +1,33 @@ +#!/usr/bin/python3 + +import os +import shutil +import logging as log +import sys +import subprocess +from socrate import system, conf + +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) + +# Actual startup script +os.environ["FRONT_ADDRESS"] = system.resolve_address(os.environ.get("HOST_FRONT", "front")) +os.environ["IMAP_ADDRESS"] = system.resolve_address(os.environ.get("HOST_IMAP", "imap")) + +os.environ["MAX_FILESIZE"] = str(int(int(os.environ.get("MESSAGE_SIZE_LIMIT"))*0.66/1048576)) + +base = "/data/_data_/_default_/" +shutil.rmtree(base + "domains/", ignore_errors=True) +os.makedirs(base + "domains", exist_ok=True) +os.makedirs(base + "configs", exist_ok=True) + +conf.jinja("/defaults/default.ini", os.environ, "/data/_data_/_default_/domains/default.ini") +conf.jinja("/defaults/application.ini", os.environ, "/data/_data_/_default_/configs/application.ini") +conf.jinja("/defaults/php.ini", os.environ, "/etc/php7/php.ini") +# Start the fastcgi process manager now that config files have been adjusted +os.system("php-fpm7") + +os.system("chown -R nginx:nginx /data") +os.system("chmod -R a+rX /var/www/snappymail/") + +subprocess.call(["/config.py"]) +os.execv("/usr/sbin/nginx", ["nginx", "-g", "daemon off;"])