From 3826de8c6072e385dd2e0a0b6b3e98e719f889f3 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 9 Aug 2025 15:55:37 +0300 Subject: [PATCH 01/33] Add installation via docker compose (MVP 1) - Add markdown tabs blocks - Fix [Issue 604](https://github.com/chatmail/relay/issues/604) - Add `--skip-dns-check` argument to `cmdeploy run` command - Add `--force` argument to `cmdeploy init` command - Add startup for `fcgiwrap.service` - Add extended check when installing `unbound.service` - Add configuration parameters (`is_development_instance`, `use_foreign_cert_manager`, `acme_email`, `change_kernel_settings`, `fs_inotify_max_user_instances_and_watchers`) --- .gitignore | 6 + CHANGELOG.md | 29 ++ README.md | 12 +- chatmaild/src/chatmaild/config.py | 7 + chatmaild/src/chatmaild/ini/chatmail.ini.f | 19 ++ cmdeploy/pyproject.toml | 1 + cmdeploy/src/cmdeploy/__init__.py | 50 ++-- cmdeploy/src/cmdeploy/cmdeploy.py | 55 +++- cmdeploy/src/cmdeploy/www.py | 3 +- docker/chatmail_server.dockerfile | 102 +++++++ docker/docker-compose-default.yaml | 58 ++++ docker/docker-compose-traefik.yaml | 115 ++++++++ docker/example.env | 4 + docker/files/entrypoint.sh | 20 ++ docker/files/setup_chatmail.service | 14 + docker/files/setup_chatmail_docker.sh | 74 +++++ docker/files/update_ini.sh | 79 ++++++ docs/DOCKER_INSTALLATION_EN.md | 310 +++++++++++++++++++++ docs/DOCKER_INSTALLATION_RU.md | 284 +++++++++++++++++++ www/src/index.md | 32 ++- www/src/info.md | 47 ++++ www/src/main.css | 54 ++++ www/src/page-layout.html | 2 +- www/src/privacy.md | 197 +++++++++++++ 24 files changed, 1535 insertions(+), 39 deletions(-) create mode 100644 docker/chatmail_server.dockerfile create mode 100644 docker/docker-compose-default.yaml create mode 100644 docker/docker-compose-traefik.yaml create mode 100644 docker/example.env create mode 100755 docker/files/entrypoint.sh create mode 100644 docker/files/setup_chatmail.service create mode 100755 docker/files/setup_chatmail_docker.sh create mode 100644 docker/files/update_ini.sh create mode 100644 docs/DOCKER_INSTALLATION_EN.md create mode 100644 docs/DOCKER_INSTALLATION_RU.md diff --git a/.gitignore b/.gitignore index 6e1054d07..c6260e934 100644 --- a/.gitignore +++ b/.gitignore @@ -164,3 +164,9 @@ cython_debug/ #.idea/ chatmail.zone + +# docker +/data/ +/custom/ +docker-compose.yaml +.env diff --git a/CHANGELOG.md b/CHANGELOG.md index e754da0d6..4669c506f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,35 @@ ## untagged +- Add installation via docker compose (MVP 1). The instructions, known issues and limitations are located in `/docs` + ([#614](https://github.com/chatmail/relay/pull/614)) + +- Add markdown tabs blocks for rendering multilingual pages. Add russian language support to `index.md`, `privacy.md`, and `info.md`. + ([#614](https://github.com/chatmail/relay/pull/614)) + +- Fix [Issue 604](https://github.com/chatmail/relay/issues/604), now the `--ssh_host` argument of the `cmdeploy run` command works correctly and does not depend on `config.mail_domain`. + ([#614](https://github.com/chatmail/relay/pull/614)) + +- Add `--skip-dns-check` argument to `cmdeploy run` command, which disables DNS record checking before installation. + ([#614](https://github.com/chatmail/relay/pull/614)) + +- Add `--force` argument to `cmdeploy init` command, which recreates the `config.ini` file. + ([#614](https://github.com/chatmail/relay/pull/614)) + +- Add startup for `fcgiwrap.service` because sometimes it did not start automatically. + ([#614](https://github.com/chatmail/relay/pull/614)) + +- Add extended check when installing `unbound.service`. Now, if it is not shown who exactly is occupying port 53, but `unbound.service` is running, it is considered that the port is occupied by `unbound.service`. + ([#614](https://github.com/chatmail/relay/pull/614)) + +- Add configuration parameters + ([#614](https://github.com/chatmail/relay/pull/614)): + - `is_development_instance` - Indicates that this instance is installed as a temporary/test one (default: `True`) + - `use_foreign_cert_manager` - Use a third-party certificate manager instead of acmetool (default: `False`) + - `acme_email` - Email address used by acmetool to obtain Let's Encrypt certificates (default: empty) + - `change_kernel_settings` - Whether to change kernel parameters during installation (default: `True`) + - `fs_inotify_max_user_instances_and_watchers` - Value for kernel parameters `fs.inotify.max_user_instances` and `fs.inotify.max_user_watches` (default: `65535`) + - Expire push notification tokens after 90 days ([#583](https://github.com/chatmail/relay/pull/583)) diff --git a/README.md b/README.md index 7ba08fdb8..5e1d579bb 100644 --- a/README.md +++ b/README.md @@ -74,22 +74,23 @@ Please substitute it with your own domain. ``` git clone https://github.com/chatmail/relay cd relay - scripts/initenv.sh ``` -3. On your local PC, create chatmail configuration file `chatmail.ini`: +### Manual installation +1. On your local PC, create chatmail configuration file `chatmail.ini`: ``` + scripts/initenv.sh scripts/cmdeploy init chat.example.org # <-- use your domain ``` -4. Verify that SSH root login to your remote server works: +2. Verify that SSH root login to your remote server works: ``` ssh root@chat.example.org # <-- use your domain ``` -5. From your local PC, deploy the remote chatmail relay server: +3. From your local PC, deploy the remote chatmail relay server: ``` scripts/cmdeploy run @@ -99,6 +100,9 @@ Please substitute it with your own domain. which you should configure at your DNS provider (it can take some time until they are public). +### Docker installation +Installation using docker compose is presented [here](./docs/DOCKER_INSTALLATION_EN.md) + ### Other helpful commands To check the status of your remotely running chatmail service: diff --git a/chatmaild/src/chatmaild/config.py b/chatmaild/src/chatmaild/config.py index 0cac3ea4b..ab9d77ac2 100644 --- a/chatmaild/src/chatmaild/config.py +++ b/chatmaild/src/chatmaild/config.py @@ -33,6 +33,7 @@ def __init__(self, inipath, params): self.password_min_length = int(params["password_min_length"]) self.passthrough_senders = params["passthrough_senders"].split() self.passthrough_recipients = params["passthrough_recipients"].split() + self.is_development_instance = params.get("is_development_instance", "true").lower() == "true" self.filtermail_smtp_port = int(params["filtermail_smtp_port"]) self.filtermail_smtp_port_incoming = int( params["filtermail_smtp_port_incoming"] @@ -43,6 +44,12 @@ def __init__(self, inipath, params): ) self.mtail_address = params.get("mtail_address") self.disable_ipv6 = params.get("disable_ipv6", "false").lower() == "true" + self.use_foreign_cert_manager = params.get("use_foreign_cert_manager", "false").lower() == "true" + self.acme_email = params["acme_email"] + self.change_kernel_settings = params.get("change_kernel_settings", "true").lower() == "true" + self.fs_inotify_max_user_instances_and_watchers = int( + params["fs_inotify_max_user_instances_and_watchers"] + ) self.imap_rawlog = params.get("imap_rawlog", "false").lower() == "true" if "iroh_relay" not in params: self.iroh_relay = "https://" + params["mail_domain"] diff --git a/chatmaild/src/chatmaild/ini/chatmail.ini.f b/chatmaild/src/chatmaild/ini/chatmail.ini.f index a99fb508d..13926745b 100644 --- a/chatmaild/src/chatmaild/ini/chatmail.ini.f +++ b/chatmaild/src/chatmaild/ini/chatmail.ini.f @@ -49,6 +49,9 @@ # Deployment Details # +# if set to "True" on main page will be showed dev banner +is_development_instance = True + # SMTP outgoing filtermail and reinjection filtermail_smtp_port = 10080 postfix_reinject_port = 10025 @@ -60,6 +63,22 @@ # if set to "True" IPv6 is disabled disable_ipv6 = False +# if you set "True", acmetool will not be installed and you will have to manage certificates yourself. +use_foreign_cert_manager = False + +# Your email adress, which will be used in acmetool to manage Let's Encrypt SSL certificates. Required if `use_foreign_cert_manager` param set as "False". +acme_email = + +# +# Kernel settings +# + +# if you set "True", the kernel settings will be configured according to the values below +change_kernel_settings = True + +# change fs.inotify.max_user_instances and fs.inotify.max_user_watches kernel settings +fs_inotify_max_user_instances_and_watchers = 65535 + # Defaults to https://iroh.{{mail_domain}} and running `iroh-relay` on the chatmail # service. # If you set it to anything else, the service will be disabled diff --git a/cmdeploy/pyproject.toml b/cmdeploy/pyproject.toml index 9f2257b56..adf53cf9c 100644 --- a/cmdeploy/pyproject.toml +++ b/cmdeploy/pyproject.toml @@ -20,6 +20,7 @@ dependencies = [ "pytest-xdist", "execnet", "imap_tools", + "pymdown-extensions", ] [project.scripts] diff --git a/cmdeploy/src/cmdeploy/__init__.py b/cmdeploy/src/cmdeploy/__init__.py index cb8947501..58491fb37 100644 --- a/cmdeploy/src/cmdeploy/__init__.py +++ b/cmdeploy/src/cmdeploy/__init__.py @@ -15,7 +15,7 @@ from pyinfra.api import FactBase from pyinfra.facts.files import File from pyinfra.facts.server import Sysctl -from pyinfra.facts.systemd import SystemdEnabled +from pyinfra.facts.systemd import SystemdEnabled, SystemdStatus from pyinfra.operations import apt, files, pip, server, systemd from .acmetool import deploy_acmetool @@ -395,20 +395,21 @@ def _configure_dovecot(config: Config, debug: bool = False) -> bool: config=config, ) - # as per https://doc.dovecot.org/configuration_manual/os/ + # as per https://doc.dovecot.org/2.3/configuration_manual/os/ # it is recommended to set the following inotify limits - for name in ("max_user_instances", "max_user_watches"): - key = f"fs.inotify.{name}" - if host.get_fact(Sysctl)[key] > 65535: - # Skip updating limits if already sufficient - # (enables running in incus containers where sysctl readonly) - continue - server.sysctl( - name=f"Change {key}", - key=key, - value=65535, - persist=True, - ) + if config.change_kernel_settings: + for name in ("max_user_instances", "max_user_watches"): + key = f"fs.inotify.{name}" + if host.get_fact(Sysctl)[key] == config.fs_inotify_max_user_instances_and_watchers: + # Skip updating limits if already sufficient + # (enables running in incus containers where sysctl readonly) + continue + server.sysctl( + name=f"Change {key}", + key=key, + value=config.fs_inotify_max_user_instances_and_watchers, + persist=True, + ) timezone_env = files.line( name="Set TZ environment variable", @@ -676,8 +677,10 @@ def deploy_chatmail(config_path: Path, disable_mail: bool) -> None: from cmdeploy.cmdeploy import Out process_on_53 = host.get_fact(Port, port=53) + if host.get_fact(SystemdStatus, services="unbound").get("unbound.service"): + process_on_53 = "unbound" if process_on_53 not in (None, "unbound"): - Out().red(f"Can't install unbound: port 53 is occupied by: {process_on_53}") + Out().red(f"Can't install unbound: port 53 is occupied by: {process_on_53}") exit(1) apt.packages( name="Install unbound", @@ -700,10 +703,12 @@ def deploy_chatmail(config_path: Path, disable_mail: bool) -> None: deploy_iroh_relay(config) # Deploy acmetool to have TLS certificates. - tls_domains = [mail_domain, f"mta-sts.{mail_domain}", f"www.{mail_domain}"] - deploy_acmetool( - domains=tls_domains, - ) + if not config.use_foreign_cert_manager: + tls_domains = [mail_domain, f"mta-sts.{mail_domain}", f"www.{mail_domain}"] + deploy_acmetool( + email = config.acme_email, + domains=tls_domains, + ) apt.packages( # required for setfacl for echobot @@ -783,6 +788,13 @@ def deploy_chatmail(config_path: Path, disable_mail: bool) -> None: enabled=True, restarted=nginx_need_restart, ) + + systemd.service( + name="Start and enable fcgiwrap", + service="fcgiwrap.service", + running=True, + enabled=True, + ) # This file is used by auth proxy. # https://wiki.debian.org/EtcMailName diff --git a/cmdeploy/src/cmdeploy/cmdeploy.py b/cmdeploy/src/cmdeploy/cmdeploy.py index 591877344..78b6ad029 100644 --- a/cmdeploy/src/cmdeploy/cmdeploy.py +++ b/cmdeploy/src/cmdeploy/cmdeploy.py @@ -32,17 +32,28 @@ def init_cmd_options(parser): action="store", help="fully qualified DNS domain name for your chatmail instance", ) + parser.add_argument( + "--force", + dest="recreate_ini", + action="store_true", + help="force reacreate ini file", + ) def init_cmd(args, out): """Initialize chatmail config file.""" mail_domain = args.chatmail_domain + inipath = args.inipath if args.inipath.exists(): - print(f"Path exists, not modifying: {args.inipath}") - return 1 - else: - write_initial_config(args.inipath, mail_domain, overrides={}) - out.green(f"created config file for {mail_domain} in {args.inipath}") + if not args.recreate_ini: + out.green(f"[WARNING] Path exists, not modifying: {inipath}") + return 0 + else: + out.yellow(f"[WARNING] Force argument was provided, deleting config file: {inipath}") + inipath.unlink() + + write_initial_config(inipath, mail_domain, overrides={}) + out.green(f"created config file for {mail_domain} in {inipath}") def run_cmd_options(parser): @@ -63,6 +74,12 @@ def run_cmd_options(parser): dest="ssh_host", help="specify an SSH host to deploy to; uses mail_domain from chatmail.ini by default", ) + parser.add_argument( + "--skip-dns-check", + dest="dns_check_disabled", + action="store_true", + help="disable checks nslookup for dns", + ) def run_cmd(args, out): @@ -70,9 +87,10 @@ def run_cmd(args, out): sshexec = args.get_sshexec() require_iroh = args.config.enable_iroh_relay - remote_data = dns.get_initial_remote_data(sshexec, args.config.mail_domain) - if not dns.check_initial_remote_data(remote_data, print=out.red): - return 1 + if not args.dns_check_disabled: + remote_data = dns.get_initial_remote_data(sshexec, args.config.mail_domain) + if not dns.check_initial_remote_data(remote_data, print=out.red): + return 1 env = os.environ.copy() env["CHATMAIL_INI"] = args.inipath @@ -89,6 +107,9 @@ def run_cmd(args, out): try: retcode = out.check_call(cmd, env=env) if retcode == 0: + server_deployed_message = f"Chatmail server started: https://{args.config.mail_domain}/" + delimiter_line = "=" * len(server_deployed_message) + out.green(f"{delimiter_line}\n{server_deployed_message}\n{delimiter_line}") out.green("Deploy completed, call `cmdeploy dns` next.") elif not remote_data["acme_account_url"]: out.red("Deploy completed but letsencrypt not configured") @@ -251,8 +272,17 @@ def red(self, msg, file=sys.stderr): def green(self, msg, file=sys.stderr): print(colored(msg, "green"), file=file) - def __call__(self, msg, red=False, green=False, file=sys.stdout): - color = "red" if red else ("green" if green else None) + def yellow(self, msg, file=sys.stderr): + print(colored(msg, "yellow"), file=file) + + def __call__(self, msg, red=False, green=False, yellow=False, file=sys.stdout): + color = None + if red: + color = "red" + elif green: + color = "green" + elif yellow: + color = "yellow" print(colored(msg, color), file=file) def check_call(self, arg, env=None, quiet=False): @@ -331,8 +361,9 @@ def main(args=None): return parser.parse_args(["-h"]) def get_sshexec(): - print(f"[ssh] login to {args.config.mail_domain}") - return SSHExec(args.config.mail_domain, verbose=args.verbose) + host = args.ssh_host if hasattr(args, "ssh_host") and args.ssh_host else args.config.mail_domain + print(f"[ssh] login to {host}") + return SSHExec(host, verbose=args.verbose) args.get_sshexec = get_sshexec diff --git a/cmdeploy/src/cmdeploy/www.py b/cmdeploy/src/cmdeploy/www.py index 9dd404ba8..2d54d9ea6 100644 --- a/cmdeploy/src/cmdeploy/www.py +++ b/cmdeploy/src/cmdeploy/www.py @@ -25,7 +25,8 @@ def prepare_template(source): assert source.exists(), source render_vars = {} render_vars["pagename"] = "home" if source.stem == "index" else source.stem - render_vars["markdown_html"] = markdown.markdown(source.read_text()) + # tabs usage for multiple languages https://facelessuser.github.io/pymdown-extensions/extensions/blocks/plugins/tab/ + render_vars["markdown_html"] = markdown.markdown(source.read_text(), extensions=['pymdownx.blocks.tab']) page_layout = source.with_name("page-layout.html").read_text() return render_vars, page_layout diff --git a/docker/chatmail_server.dockerfile b/docker/chatmail_server.dockerfile new file mode 100644 index 000000000..72c4a042c --- /dev/null +++ b/docker/chatmail_server.dockerfile @@ -0,0 +1,102 @@ +FROM jrei/systemd-debian:12 AS base + +ENV LANG=en_US.UTF-8 + +RUN echo 'APT::Install-Recommends "0";' > /etc/apt/apt.conf.d/01norecommend && \ + echo 'APT::Install-Suggests "0";' >> /etc/apt/apt.conf.d/01norecommend && \ + apt-get update && \ + apt-get install -y \ + ca-certificates && \ + DEBIAN_FRONTEND=noninteractive \ + TZ=Europe/London \ + apt-get install -y tzdata && \ + apt-get install -y locales && \ + sed -i -e "s/# $LANG.*/$LANG UTF-8/" /etc/locale.gen && \ + dpkg-reconfigure --frontend=noninteractive locales && \ + update-locale LANG=$LANG \ + && rm -rf /var/lib/apt/lists/* + +RUN apt-get update && \ + apt-get install -y \ + openssh-client \ + openssh-server \ + git \ + python3 \ + python3-venv \ + python3-virtualenv \ + gcc \ + python3-dev \ + opendkim \ + opendkim-tools \ + curl \ + rsync \ + unbound \ + unbound-anchor \ + dnsutils \ + postfix \ + acl \ + nginx \ + libnginx-mod-stream \ + fcgiwrap \ + cron \ + && for pkg in core imapd lmtpd; do \ + case "$pkg" in \ + core) sha256="43f593332e22ac7701c62d58b575d2ca409e0f64857a2803be886c22860f5587" ;; \ + imapd) sha256="8d8dc6fc00bbb6cdb25d345844f41ce2f1c53f764b79a838eb2a03103eebfa86" ;; \ + lmtpd) sha256="2f69ba5e35363de50962d42cccbfe4ed8495265044e244007d7ccddad77513ab" ;; \ + esac; \ + url="https://download.delta.chat/dovecot/dovecot-${pkg}_2.3.21%2Bdfsg1-3_amd64.deb"; \ + file="/tmp/$(basename "$url")"; \ + curl -fsSL "$url" -o "$file"; \ + echo "$sha256 $file" | sha256sum -c -; \ + apt-get install -y "$file"; \ + rm -f "$file"; \ + done \ + && rm -rf /var/lib/apt/lists/* + +RUN systemctl enable \ + ssh \ + fcgiwrap + +RUN sed -i 's/^#PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config && \ + sed -i 's/^#PermitRootLogin .*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config && \ + ssh-keygen -P "" -t rsa -b 2048 -f /root/.ssh/id_rsa && \ + mkdir -p /root/.ssh && \ + cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys && \ + SSH_USER_CONFIG="/root/.ssh/config" && \ + echo "Host localhost" > "$SSH_USER_CONFIG" && \ + echo " HostName localhost" >> "$SSH_USER_CONFIG" && \ + echo " User root" >> "$SSH_USER_CONFIG" && \ + echo " StrictHostKeyChecking no" >> "$SSH_USER_CONFIG" && \ + echo " UserKnownHostsFile /dev/null" >> "$SSH_USER_CONFIG" + ## TODO: deny access for all insteed root form 127.0.0.1 https://unix.stackexchange.com/a/406264 + +WORKDIR /opt/chatmail + +ARG SETUP_CHATMAIL_SERVICE_PATH=/lib/systemd/system/setup_chatmail.service +COPY ./files/setup_chatmail.service "$SETUP_CHATMAIL_SERVICE_PATH" +RUN ln -sf "$SETUP_CHATMAIL_SERVICE_PATH" "/etc/systemd/system/multi-user.target.wants/setup_chatmail.service" + +COPY --chmod=555 ./files/setup_chatmail_docker.sh /setup_chatmail_docker.sh +COPY --chmod=555 ./files/update_ini.sh /update_ini.sh +COPY --chmod=555 ./files/entrypoint.sh /entrypoint.sh + +## TODO: add git clone. +## Problem: how correct save only required files inside container.... +# RUN git clone https://github.com/chatmail/relay.git -b master . \ +# && ./scripts/initenv.sh + +# EXPOSE 443 25 587 143 993 + +VOLUME ["/sys/fs/cgroup", "/home"] + +STOPSIGNAL SIGRTMIN+3 + +ENTRYPOINT ["/entrypoint.sh"] + +CMD [ "--default-standard-output=journal+console", \ + "--default-standard-error=journal+console" ] + +## TODO: Add installation and configuration of chatmaild inside the Dockerfile. +## This is required to ensure repeatable deployment. +## In the current MVP, the chatmaild server is updated on every container restart. diff --git a/docker/docker-compose-default.yaml b/docker/docker-compose-default.yaml new file mode 100644 index 000000000..61e7765f2 --- /dev/null +++ b/docker/docker-compose-default.yaml @@ -0,0 +1,58 @@ +services: + chatmail: + build: + context: ./docker + dockerfile: chatmail_server.dockerfile + tags: + - chatmail-relay:latest + image: chatmail-relay:latest + restart: unless-stopped + container_name: chatmail + cgroup: host # required for systemd + tty: true # required for logs + tmpfs: # required for systemd + - /tmp + - /run + - /run/lock + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + environment: + MAIL_DOMAIN: + CHANGE_KERNEL_SETTINGS: "False" + ACME_EMAIL: + + MAX_MESSAGE_SIZE: "50M" + # DEBUG_COMMANDS_ENABLED: "true" + # FORCE_REINIT_INI_FILE: "true" + # USE_FOREIGN_CERT_MANAGER: "True" + # ENABLE_CERTS_MONITORING: "true" + # CERTS_MONITORING_TIMEOUT: 10 + # IS_DEVELOPMENT_INSTANCE: "True" + ports: + - "25:25" + - "587:587" + - "143:143" + - "993:993" + - "443:443" + volumes: + ## system + - /sys/fs/cgroup:/sys/fs/cgroup:rw # required for systemd + - ./:/opt/chatmail + - ./data/acme:/var/lib/acme + + ## data + - ./data/chatmail:/home + - ./data/chatmail-dkimkeys:/etc/dkimkeys + - ./data/chatmail-echobot:/run/echobot + - ./data/chatmail-acme:/var/lib/acme + + ## custom resources + # - ./custom/www/src/index.md:/opt/chatmail/www/src/index.md + + ## debug + # - ./docker/files/setup_chatmail_docker.sh:/setup_chatmail_docker.sh + # - ./docker/files/entrypoint.sh:/entrypoint.sh + # - ./docker/files/update_ini.sh:/update_ini.sh diff --git a/docker/docker-compose-traefik.yaml b/docker/docker-compose-traefik.yaml new file mode 100644 index 000000000..1601578f9 --- /dev/null +++ b/docker/docker-compose-traefik.yaml @@ -0,0 +1,115 @@ +services: + chatmail: + build: + context: ./docker + dockerfile: chatmail_server.dockerfile + tags: + - chatmail-relay:latest + image: chatmail-relay:latest + restart: unless-stopped + container_name: chatmail + depends_on: + - traefik-certs-dumper + cgroup: host # required for systemd + tty: true # required for logs + tmpfs: # required for systemd + - /tmp + - /run + - /run/lock + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + environment: #all possible variables you can check inside README and /chatmaild/src/chatmaild/ini/chatmail.ini.f + MAIL_DOMAIN: $MAIL_DOMAIN + # MAX_MESSAGE_SIZE: "50M" + # DEBUG_COMMANDS_ENABLED: "true" + # FORCE_REINIT_INI_FILE: "true" + USE_FOREIGN_CERT_MANAGER: "true" + CHANGE_KERNEL_SETTINGS: "false" + PATH_TO_SSL_CONTAINER: $PATH_TO_SSL_CONTAINER + ENABLE_CERTS_MONITORING: "true" + # CERTS_MONITORING_TIMEOUT: 60 + # IS_DEVELOPMENT_INSTANCE: "true" + ports: + - "25:25" + - "587:587" + - "143:143" + - "993:993" + volumes: + ## system + - /sys/fs/cgroup:/sys/fs/cgroup:rw # required for systemd + - ./:/opt/chatmail + - ${PATH_TO_SSL_HOST}:${PATH_TO_SSL_CONTAINER}:ro + + ## data + - ./data/chatmail:/home + # - ./data/chatmail-dkimkeys:/etc/dkimkeys + # - ./data/chatmail-echobot:/run/echobot + # - ./data/chatmail-acme:/var/lib/acme + + ## custom resources + # - ./custom/www/src/index.md:/opt/chatmail/www/src/index.md + + ## debug + # - ./docker/files/setup_chatmail_docker.sh:/setup_chatmail_docker.sh + # - ./docker/files/entrypoint.sh:/entrypoint.sh + # - ./docker/files/update_ini.sh:/update_ini.sh + + labels: + - traefik.enable=true + - traefik.http.services.chatmail-relay.loadbalancer.server.scheme=https + - traefik.http.services.chatmail-relay.loadbalancer.server.port=443 + - traefik.http.services.chatmail-relay.loadbalancer.serverstransport=insecure@file + - traefik.http.routers.chatmail-relay.rule=Host(`${MAIL_DOMAIN}`) || Host(`mta-sts.${MAIL_DOMAIN}`) || Host(`www.${MAIL_DOMAIN}`) + - traefik.http.routers.chatmail-relay.service=chatmail-relay + - traefik.http.routers.chatmail-relay.tls=true + - traefik.http.routers.chatmail-relay.tls.certresolver=letsEncrypt + + traefik: + image: traefik:v3.3 + container_name: traefik + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + command: + - --configFile=/config.yaml + # ports: + # - "80:80" + # - "443:443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./data/traefik/config.yaml:/config.yaml + - ./data/traefik/acme.json:/acme.json + - ./data/traefik/dynamic-configs:/dynamic/conf + + network_mode: host + + traefik-certs-dumper: + image: ldez/traefik-certs-dumper:v2.10.0 + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + depends_on: + - traefik + entrypoint: sh -c ' + apk add openssl && + while ! [ -e /data/acme.json ] + || ! [ `jq ".[] | .Certificates | length" /data/acme.json | jq -s "add" ` != 0 ]; do + sleep 1 + ; done + && traefik-certs-dumper file --version v3 --watch --domain-subdir=true + --source /data/acme.json --dest /data/letsencrypt/certs --post-hook "sh /post-hook.sh"' + environment: + CERTS_DIR: /data/letsencrypt/certs + volumes: + - ./data/traefik/letsencrypt:/data/letsencrypt + - ./data/traefik/acme.json:/data/acme.json + - ./data/traefik/post-hook.sh:/post-hook.sh diff --git a/docker/example.env b/docker/example.env new file mode 100644 index 000000000..bb63a0559 --- /dev/null +++ b/docker/example.env @@ -0,0 +1,4 @@ +MAIL_DOMAIN="chat.example.com" + +PATH_TO_SSL_HOST="/opt/traefik/data/letsencrypt/certs/${MAIL_DOMAIN}" +PATH_TO_SSL_CONTAINER="/var/lib/acme/live/${MAIL_DOMAIN}" diff --git a/docker/files/entrypoint.sh b/docker/files/entrypoint.sh new file mode 100755 index 000000000..b704c2e3a --- /dev/null +++ b/docker/files/entrypoint.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -eo pipefail + +if [ "${USE_FOREIGN_CERT_MANAGER,,}" == "true" ]; then + if [ ! -f "$PATH_TO_SSL_CONTAINER/fullchain" ]; then + echo "Error: file '$PATH_TO_SSL_CONTAINER/fullchain' does not exist. Exiting..." > /dev/stderr + exit 1 + fi + if [ ! -f "$PATH_TO_SSL_CONTAINER/privkey" ]; then + echo "Error: file '$PATH_TO_SSL_CONTAINER/privkey' does not exist. Exiting..." > /dev/stderr + exit 1 + fi +fi + +SETUP_CHATMAIL_SERVICE_PATH="${SETUP_CHATMAIL_SERVICE_PATH:-/lib/systemd/system/setup_chatmail.service}" + +env_vars=$(printenv | cut -d= -f1 | xargs) +sed -i "s||$env_vars|g" $SETUP_CHATMAIL_SERVICE_PATH + +exec /lib/systemd/systemd $@ diff --git a/docker/files/setup_chatmail.service b/docker/files/setup_chatmail.service new file mode 100644 index 000000000..2a0a48bc0 --- /dev/null +++ b/docker/files/setup_chatmail.service @@ -0,0 +1,14 @@ +[Unit] +Description=Run container setup commands +After=multi-user.target +ConditionPathExists=/setup_chatmail_docker.sh + +[Service] +Type=oneshot +ExecStart=/bin/bash /setup_chatmail_docker.sh +RemainAfterExit=true +WorkingDirectory=/opt/chatmail +PassEnvironment= + +[Install] +WantedBy=multi-user.target diff --git a/docker/files/setup_chatmail_docker.sh b/docker/files/setup_chatmail_docker.sh new file mode 100755 index 000000000..7e1017913 --- /dev/null +++ b/docker/files/setup_chatmail_docker.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +set -eo pipefail +export INI_FILE="${INI_FILE:-chatmail.ini}" +export ENABLE_CERTS_MONITORING="${ENABLE_CERTS_MONITORING:-true}" +export CERTS_MONITORING_TIMEOUT="${CERTS_MONITORING_TIMEOUT:-60}" +export PATH_TO_SSL_CONTAINER="${PATH_TO_SSL_CONTAINER:-/var/lib/acme/live/${MAIL_DOMAIN}}" +export CHANGE_KERNEL_SETTINGS=${CHANGE_KERNEL_SETTINGS:-"False"} + +if [ -z "$MAIL_DOMAIN" ]; then + echo "ERROR: Environment variable 'MAIL_DOMAIN' must be set!" >&2 + exit 1 +fi + +debug_commands() { + echo "Executing debug commands" + # git config --global --add safe.directory /opt/chatmail + # ./scripts/initenv.sh +} + +calculate_hash() { + find "$PATH_TO_SSL_CONTAINER" -type f -exec sha1sum {} \; | sort | sha1sum | awk '{print $1}' +} + +monitor_certificates() { + if [ "$ENABLE_CERTS_MONITORING" != "true" ]; then + echo "Certs monitoring disabled." + exit 0 + fi + + current_hash=$(calculate_hash) + previous_hash=$current_hash + + while true; do + current_hash=$(calculate_hash) + if [[ "$current_hash" != "$previous_hash" ]]; then + # TODO: add an option to restart at a specific time interval + echo "[INFO] Certificate's folder hash was changed, restarting nginx, dovecot and postfix services." + systemctl restart nginx.service + systemctl reload dovecot.service + systemctl reload postfix.service + previous_hash=$current_hash + fi + sleep $CERTS_MONITORING_TIMEOUT + done +} + +### MAIN + +if [ "$DEBUG_COMMANDS_ENABLED" == "true" ]; then + debug_commands +fi + +if [ "$FORCE_REINIT_INI_FILE" == "true" ]; then + INI_CMD_ARGS=--force +fi + +/usr/sbin/opendkim-genkey -D /etc/dkimkeys -d $MAIL_DOMAIN -s opendkim +chown opendkim:opendkim /etc/dkimkeys/opendkim.private +chown opendkim:opendkim /etc/dkimkeys/opendkim.txt + +# TODO: Move to debug_commands after git clone is moved to dockerfile. +git config --global --add safe.directory /opt/chatmail +./scripts/initenv.sh + +./scripts/cmdeploy init --config "${INI_FILE}" $INI_CMD_ARGS $MAIL_DOMAIN +bash /update_ini.sh + +./scripts/cmdeploy run --ssh-host localhost --skip-dns-check + +echo "ForwardToConsole=yes" >> /etc/systemd/journald.conf +systemctl restart systemd-journald + +monitor_certificates & diff --git a/docker/files/update_ini.sh b/docker/files/update_ini.sh new file mode 100644 index 000000000..c5d65661a --- /dev/null +++ b/docker/files/update_ini.sh @@ -0,0 +1,79 @@ +#!/bin/bash +set -eo pipefail + +INI_FILE="${INI_FILE:-chatmail.ini}" + +if [ ! -f "$INI_FILE" ]; then + echo "Error: file $INI_FILE not found." >&2 + exit 1 +fi + +TMP_FILE="$(mktemp)" + +convert_to_bytes() { + local value="$1" + if [[ "$value" =~ ^([0-9]+)([KkMmGgTt])$ ]]; then + local num="${BASH_REMATCH[1]}" + local unit="${BASH_REMATCH[2]}" + case "$unit" in + [Kk]) echo $((num * 1024)) ;; + [Mm]) echo $((num * 1024 * 1024)) ;; + [Gg]) echo $((num * 1024 * 1024 * 1024)) ;; + [Tt]) echo $((num * 1024 * 1024 * 1024 * 1024)) ;; + esac + elif [[ "$value" =~ ^[0-9]+$ ]]; then + echo "$value" + else + echo "Error: incorrect size format: $value." >&2 + return 1 + fi +} + +process_specific_params() { + local key=$1 + local value=$2 + local destination_file=$3 + + if [[ "$key" == "max_message_size" ]]; then + converted=$(convert_to_bytes "$value") || exit 1 + if grep -q -e "## .* = .* bytes" "$destination_file"; then + sed "s|## .* = .* bytes|## $value = $converted bytes|g" "$destination_file"; + else + echo "## $value = $converted bytes" >> "$destination_file" + fi + echo "$key = $converted" >> "$destination_file" + else + echo "$key = $value" >> "$destination_file" + fi +} + +while IFS= read -r line; do + if [[ "$line" =~ ^[[:space:]]*#.* || "$line" =~ ^[[:space:]]*$ ]]; then + echo "$line" >> "$TMP_FILE" + continue + fi + + if [[ "$line" =~ ^([a-z0-9_]+)[[:space:]]*=[[:space:]]*(.*)$ ]]; then + key="${BASH_REMATCH[1]}" + current_value="${BASH_REMATCH[2]}" + env_var_name=$(echo "$key" | tr 'a-z' 'A-Z') + env_value="${!env_var_name}" + + if [[ -n "$env_value" ]]; then + process_specific_params "$key" "$env_value" "$TMP_FILE" + else + echo "$line" >> "$TMP_FILE" + fi + else + echo "$line" >> "$TMP_FILE" + fi +done < "$INI_FILE" + +PERMS=$(stat -c %a "$INI_FILE") +OWNER=$(stat -c %u "$INI_FILE") +GROUP=$(stat -c %g "$INI_FILE") + +chmod "$PERMS" "$TMP_FILE" +chown "$OWNER":"$GROUP" "$TMP_FILE" + +mv "$TMP_FILE" "$INI_FILE" diff --git a/docs/DOCKER_INSTALLATION_EN.md b/docs/DOCKER_INSTALLATION_EN.md new file mode 100644 index 000000000..c6c44bb80 --- /dev/null +++ b/docs/DOCKER_INSTALLATION_EN.md @@ -0,0 +1,310 @@ +# Known issues and limitations + +- Installation using acmetool (`docker-compose-default.yaml`) may NOT work. In this case, use installation via traefik (`docker-compose-traefik.yaml`). Personally, during my tests, I encountered the error `could not install DNS challenge, no hooks succeeded;`, which I was unable to fix. +- Chatmail will be reinstalled every time the container is started (longer the first time, faster on subsequent starts). This is how the original installer works because it wasn’t designed for Docker. At the end of the documentation, there’s a [proposed solution](#locking-the-chatmail-version). +- Requires cgroups v2 configured in the system. Operation with cgroups v1 has not been tested. +- Yes, of course, using systemd inside a container is a hack, and it would be better to split it into several services, but since this is an MVP, it turned out to be easier to do it this way initially than to rewrite the entire deployment system. +- The Docker image is only suitable for amd64. If you need to run it on a different architecture, try modifying the Dockerfile (specifically the part responsible for installing dovecot). + +# Docker installation +This section provides instructions for installing Chatmail using docker-compose. + +## Preliminary setup +We use `chat.example.org` as the Chatmail domain in the following steps. +Please substitute it with your own domain. + +1. Setup the initial DNS records. + The following is an example in the familiar BIND zone file format with + a TTL of 1 hour (3600 seconds). + Please substitute your domain and IP addresses. + + ``` + chat.example.com. 3600 IN A 198.51.100.5 + chat.example.com. 3600 IN AAAA 2001:db8::5 + www.chat.example.com. 3600 IN CNAME chat.example.com. + mta-sts.chat.example.com. 3600 IN CNAME chat.example.com. + ``` + +2. clone the repository on your server. + + ```shell + git clone https://github.com/chatmail/relay + cd relay + ``` + +## Installation +When installing via Docker, there are several options: + +- Use the built-in nginx and acmetool in Chatmail container to host the chat and manage certificates. +- Use third-party tools for certificate management. + +For the third-party certificate manager example, traefik will be used, but you can use whatever is more convenient for you. + +1. Copy the file `./docker/docker-compose-default.yaml` or `./docker/docker-compose-traefik.yaml` and rename it to `docker-compose.yaml`. This is necessary because `docker-compose.yaml` is in `.gitignore` and won’t cause conflicts when updating the git repository. + +```shell +cp ./docker/docker-compose-default.yaml docker-compose.yaml +## or +# cp ./docker/docker-compose-traefik.yaml docker-compose.yaml +``` + +2. Copy `./docker/example.env` and rename it to `.env`. This file stores variables used in `docker-compose.yaml`. Required only when installing with traefik; if using the default setup, you can skip this step. + +```shell +cp ./docker/example.env .env +``` + +3. Configure kernel parameters because they cannot be changed inside the container, specifically `fs.inotify.max_user_instances` and `fs.inotify.max_user_watches`. Run the following: + +```shell +echo "fs.inotify.max_user_instances=65536" | sudo tee -a /etc/sysctl.d/99-inotify.conf +echo "fs.inotify.max_user_watches=65536" | sudo tee -a /etc/sysctl.d/99-inotify.conf +sudo sysctl --system +``` + +4. Configure container environment variables. Below is the list of variables used during deployment: + +- `MAIL_DOMAIN` – The domain name of the future server. (required) +- `DEBUG_COMMANDS_ENABLED` – Run debug commands before installation. (default: `false`) +- `FORCE_REINIT_INI_FILE` – Recreate the ini configuration file on startup. (default: `false`) +- `USE_FOREIGN_CERT_MANAGER` – Use a third-party certificate manager. (default: `false`) +- `INI_FILE` – Path to the ini configuration file. (default: `./chatmail.ini`) +- `PATH_TO_SSL_CONTAINER` – Path to where the certificates are stored. (default: `/var/lib/acme/live/${MAIL_DOMAIN}`) +- `ENABLE_CERTS_MONITORING` – Enable certificate monitoring if `USE_FOREIGN_CERT_MANAGER=true`. If certificates change, services will be automatically restarted. (default: `false`) +- `CERTS_MONITORING_TIMEOUT` – Interval in seconds to check if certificates have changed. (default: `'60'`) + +You can also use any variables from the [ini configuration file](https://github.com/chatmail/relay/blob/main/chatmaild/src/chatmaild/ini/chatmail.ini.f); they must be in uppercase. + +Mandatory variables for deployment via Docker: + +- `CHANGE_KERNEL_SETTINGS` – Change kernel settings (`fs.inotify.max_user_instances` and `fs.inotify.max_user_watches`) on startup. Changing kernel settings inside the container is not possible! (default: `False`) + +5. Configure environment variables in the `.env` file. These variables are used in the `docker-compose.yaml` file to pass repeated values. + +6. Build the Docker image: + +```shell +docker compose build chatmail +``` + +
+Additional steps for configuring with traefik + +> [!note] +> If you are using the default installation without traefik – skip these steps and go to step 7 (running docker compose). + +Before starting traefik, configuration files must be prepared; otherwise, it will not start correctly. + +First, run these commands in the console, replacing their values with the correct ones: + +```shell +export YOUR_EMAIL=your_email@gmail.com +mkdir -p "./data/traefik" +cd "./data/traefik" +``` + +1. Create a traefik configuration file: + +```shell +cat > config.yaml << EOF +log: + level: TRACE + +entryPoints: + web: + address: ":80" + http: + redirections: + entryPoint: + to: websecure + permanent: true + websecure: + address: ":443" + +providers: + docker: + endpoint: "unix:///var/run/docker.sock" + exposedByDefault: false + file: + directory: /dynamic/conf + watch: true + +serverstransport: + insecureskipverify: true + +certificatesResolvers: + letsEncrypt: + acme: + email: $YOUR_EMAIL + storage: /acme.json + caServer: "https://acme-v02.api.letsencrypt.org/directory" + tlschallenge: true + httpChallenge: + entryPoint: web +EOF +``` + +2. Create a post-hook script: + +```shell +cat > post-hook.sh << 'EOF' +CERTS_DIR=${CERTS_DIR:-"/data/letsencrypt/certs"} + +for dir in "$CERTS_DIR"/*/; do + cd "$dir" + if [ -f "certificate.crt" ]; then + ln -sf certificate.crt fullchain + fi + if [ -f "privatekey.key" ]; then + ln -sf privatekey.key privkey + fi + cd - +done +EOF +``` + +3. Create the `acme.json` file: + +```shell +touch acme.json +sudo chown 0:0 ./acme.json # required +sudo chmod 600 ./acme.json # required +``` + +4. Create insecure config: + +```shell +mkdir dynamic-configs +cat > ./dynamic-configs/insecure.yaml << 'EOF' +http: + serversTransports: + insecure: + insecureSkipVerify: true +EOF +cd ../.. +``` + +
+ +7. Start docker compose and wait for the installation to finish: + +```shell +docker compose up -d # start service +docker compose logs -f chatmail # view container logs, press CTRL+C to exit +``` + +8. After installation is complete, you can open `https://` in your browser. + +## Using custom files + +When using Docker, you can apply modified configuration files to make the installation more personalized. This is usually needed for the `www/src` section so that the Chatmail landing page is customized to your taste, but it can be used for any other cases as well. + +To replace files correctly: + +1. Create the `./custom` directory. It is in `.gitignore`, so it won’t cause conflicts when updating. + +```shell +mkdir -p ./custom +``` + +2. Modify the required file. For example, `index.md`: + +```shell +mkdir -p ./custom/www/src +nano ./custom/www/src/index.md +``` + +3. In `docker-compose.yaml`, add the file mount in the `volumes` section: + +```yaml +services: + chatmail: + volumes: + ... + ## custom resources + - ./custom/www/src/index.md:/opt/chatmail/www/src/index.md +``` + +4. Restart the service: + +```shell +docker compose down +docker compose up -d +``` + +## Locking the Chatmail version + +> [!note] +> These steps are optional and should only be done if you are not satisfied that the service is installed each time the container starts. + +Since the current Docker version installs the Chatmail service every time the container starts, you can lock the container version after installation as follows: + +1. Commit the current state of the configured container: + +```shell +docker container commit chatmail configured-chatmail:$(date +'%Y-%m-%d') +docker image ls | grep configured-chatmail +``` + +2. Change the entrypoint for the container in `docker-compose.yaml` to: + +```yaml +services: + chatmail: + image: + volumes: + ... + ## custom resources + - ./custom/setup_chatmail_docker.sh:/setup_chatmail_docker.sh +``` + +3. Create the file `./custom/setup_chatmail_docker.sh` with the new configuration: + +```shell +mkdir -p ./custom +cat > ./custom/setup_chatmail_docker.sh << 'EOF' +#!/bin/bash + +set -eo pipefail + +export ENABLE_CERTS_MONITORING="${ENABLE_CERTS_MONITORING:-true}" +export CERTS_MONITORING_TIMEOUT="${CERTS_MONITORING_TIMEOUT:-60}" +export PATH_TO_SSL_CONTAINER="${PATH_TO_SSL_CONTAINER:-/var/lib/acme/live/${MAIL_DOMAIN}}" + +calculate_hash() { + find "$PATH_TO_SSL_CONTAINER" -type f -exec sha1sum {} \; | sort | sha1sum | awk '{print $1}' +} + +monitor_certificates() { + if [ "$ENABLE_CERTS_MONITORING" != "true" ]; then + echo "Certs monitoring disabled." + exit 0 + fi + + current_hash=$(calculate_hash) + previous_hash=$current_hash + + while true; do + current_hash=$(calculate_hash) + if [[ "$current_hash" != "$previous_hash" ]]; then + # TODO: add an option to restart at a specific time interval + echo "[INFO] Certificate's folder hash was changed, restarting nginx, dovecot and postfix services." + systemctl restart nginx.service + systemctl reload dovecot.service + systemctl reload postfix.service + previous_hash=$current_hash + fi + sleep $CERTS_MONITORING_TIMEOUT + done +} + +monitor_certificates & +EOF +``` + +4. Restart the service: + +```shell +docker compose down +docker compose up -d +``` diff --git a/docs/DOCKER_INSTALLATION_RU.md b/docs/DOCKER_INSTALLATION_RU.md new file mode 100644 index 000000000..36d1913aa --- /dev/null +++ b/docs/DOCKER_INSTALLATION_RU.md @@ -0,0 +1,284 @@ +# Известные проблемы и ограничения +- Установка с помощью acmetool (`docker-compose-default.yaml`) может НЕ работать. В таком случае используйте установку через traefik (`docker-compose-traefik.yaml`). Лично у меня при тестах ошибка была `could not install DNS challenge, no hooks succeeded;`, которую исправить не удалось. +- Chatmail будет переустановлен при каждом запуске контейнера (при первом - долго, при последующих быстрее). Так устроен изначальный установщик, потому что он не был заточен под docker. В конце документации [представлено](#фиксирование-версии-chatmail) возможное решение +- Требуется настроенный в системе cgroups v2. Работа с cgroups v1 не тестировалась. +- Да, понятно дело что systemd использовать в контейнере костыль и надо это всё разнести на несколько сервисов, но это MVP и в первом приближении оказалось сделать проще так, чем переписывать всю систему развертывания. +- docker образ подходит только для amd64, если нужно запустить на другой архитектуре, попробуйте изменить dockerfile (конкретно ту часть что ответсвенна за установку dovecot) + +# Docker installation +Здесь представлена инструкция по установке chatmail с помощью docker-compose. + +## Предварительная настройка +We use `chat.example.org` as the chatmail domain in the following steps. +Please substitute it with your own domain. + +1. Настройте начальные записи DNS.Ниже приведен пример в привычном формате файла зоны BIND сTTL 1 час (3600 секунд). + Замените домен и IP-адреса на свои. + + ``` + chat.example.com. 3600 IN A 198.51.100.5 + chat.example.com. 3600 IN AAAA 2001:db8::5 + www.chat.example.com. 3600 IN CNAME chat.example.com. + mta-sts.chat.example.com. 3600 IN CNAME chat.example.com. + ``` + +2. Склонируйте репозиторий на свой сервер. + + ```shell + git clone https://github.com/chatmail/relay + cd relay + ``` + +## Installation +При установке через docker есть несколько вариантов: +- использовать встроенный в chatmail контейнер nginx и acmetool для хостинга чата и управления сертификатами. +- использовать сторонние инструменты для менеджмента сертификатов + +В качестве примера для стороннего менеджера сертификатов будет использоваться traefik, но вы можете использовать то что удобнее вам. + +1. Скопировать файл `./docker/docker-compose-default.yaml` или `./docker/docker-compose-traefik.yaml` и переименовать в `docker-compose.yaml`. Это нужно потому что `docker-compose.yaml` находится в `.gitignore` и не будет создавать конфликты при обновлении гит репозитория. +```shell +cp ./docker/docker-compose-default.yaml docker-compose.yaml +## or +# cp ./docker/docker-compose-traefik.yaml docker-compose.yaml +``` + +2. Скопировать `./docker/example.env` и переименовать в `.env`. Здесь хранятся переменные, которые используятся в `docker-compose.yaml`. Нужен только для установки совместно с traefik, если используется default - можно пропустить +```shell +cp ./docker/example.env .env +``` + +3. Настроить параметры ядра, потому что внутри контейнера их нельзя изменить, а конкретно `fs.inotify.max_user_instances` и `fs.inotify.max_user_watches`. Для этого выполнить следующее: +```shell +echo "fs.inotify.max_user_instances=65536" | sudo tee -a /etc/sysctl.d/99-inotify.conf +echo "fs.inotify.max_user_watches=65536" | sudo tee -a /etc/sysctl.d/99-inotify.conf +sudo sysctl --system +``` + +4. Настроить переменные окружения контейнера. Ниже перечислен список переменных учавствующих при развертывании. +- `MAIL_DOMAIN` - Доменное имя будущего сервера. (required) +- `DEBUG_COMMANDS_ENABLED` - Выполнить debug команды перед установкой. (default: `false`) +- `FORCE_REINIT_INI_FILE` - Пересоздавать ini файл конфигурации при запуске. (default: `false`) +- `USE_FOREIGN_CERT_MANAGER` - Использовать сторонний менеджер сертификатов. (default: `false`) +- `INI_FILE` - путь к ini файлу конфигурации. (default: `./chatmail.ini`) +- `PATH_TO_SSL_CONTAINER` - Путь где располагаются сертификаты. (default: `/var/lib/acme/live/${MAIL_DOMAIN}`) +- `ENABLE_CERTS_MONITORING` - Включить мониторинг сертификатов, если `USE_FOREIGN_CERT_MANAGER=true`. Если сертфикаты изменятся сервисы будут автоматически перезапущены. (default: `false`) +- `CERTS_MONITORING_TIMEOUT` - Раз во сколько секунд проверять что изменились сертификаты. (default: `'60'`) + +Также могут быть использованы все переменные из [ini файла конфигурации](https://github.com/chatmail/relay/blob/main/chatmaild/src/chatmaild/ini/chatmail.ini.f), они обязаны быть в uppercase формате. + +Ниже перечислены переменные, которые обязательны быть выставлены при развертывании через docker: +- `CHANGE_KERNEL_SETTINGS` - Менять настройки ядра (`fs.inotify.max_user_instances` и `fs.inotify.max_user_watches`) при запуске. При запуске в контейнере смена настроек ядра не может быть выполнена! (default: `False`) + +5. Настроить переменные окружения в `.env` файле. Эти переменные используются в `docker-compose.yaml` файле, чтобы передавать повторяющиеся значения. + +6. Собрать docker образ +```shell +docker compose build chatmail +``` + +
+ +Дополнительные шаги для конфигурации работы с traefik + +> [!note] +> Если вы используете default установку, без использования traefik - пропустите эти шаги и переходите к шагу 7 (запуск docker compose) + +Перед запуском traefik необходимо подготовить файлы конфигурации, иначе он запустится некорректно. + +Сначала выполните эти команды в консоли, заменив значения в них на корректные. +```shell +export YOUR_EMAIL=your_email@gmail.com +mkdir -p "./data/traefik" +cd "./data/traefik" +``` + +1. Создать файл конфигурации traefik +```shell +cat > config.yaml << EOF +log: + level: TRACE + +entryPoints: + web: + address: ":80" + http: + redirections: + entryPoint: + to: websecure + permanent: true + websecure: + address: ":443" + +providers: + docker: + endpoint: "unix:///var/run/docker.sock" + exposedByDefault: false + file: + directory: /dynamic/conf + watch: true + +serverstransport: + insecureskipverify: true + +certificatesResolvers: + letsEncrypt: + acme: + email: $YOUR_EMAIL + storage: /acme.json + caServer: "https://acme-v02.api.letsencrypt.org/directory" + tlschallenge: true + httpChallenge: + entryPoint: web +EOF +``` + +2. Создать post-hook скрипт +```shell +cat > post-hook.sh << 'EOF' +CERTS_DIR=${CERTS_DIR:-"/data/letsencrypt/certs"} + +for dir in "$CERTS_DIR"/*/; do + cd "$dir" + if [ -f "certificate.crt" ]; then + ln -sf certificate.crt fullchain + fi + if [ -f "privatekey.key" ]; then + ln -sf privatekey.key privkey + fi + cd - +done +EOF +``` + +3. Создать `acme.json` файл +```shell +touch acme.json +sudo chown 0:0 ./acme.json # это обязательно +sudo chmod 600 ./acme.json # это обязательно +``` + +4. Создать insecure config +```shell +mkdir dynamic-configs +cat > ./dynamic-configs/insecure.yaml << 'EOF' +http: + serversTransports: + insecure: + insecureSkipVerify: true +EOF +cd ../.. +``` + +
+ +7. Запустить docker compose и дождаться завершения установки +```shell +docker compose up -d # запуск сервиса +docker compose logs -f chatmail # просмотр логов контейнера. Для выхода нажать CTRL+C +``` + +8. По окончанию установки можно открыть в браузер `https://` + +## Использование кастомных файлов +При использовании docker есть возможность использовать измененые файлы конфигурации, чтобы сделать установку более персонализированной. Обычно это требуется для секции `www/src`, чтобы ознакомительная страница Chatmail была сделана на ваш вкус. Но также это можно использовать и для любых других случаев. + +Для того чтобы корректно выполнить подмену файлов необходимо +1. создать каталог `./custom`, он находится в `.gitignore`, поэтому при обновлении не вызовет конфликтов. +```shell +mkdir -p ./custom +``` + +2. Изменить нужный файл. Для примера возьмем `index.md` +```shell +mkdir -p ./custom/www/src +nano ./custom/www/src/index.md +``` + +3. В `docker-compose.yaml` добавить монтирование файла с помощью секции `volumes` +```yaml +services: + chatmail: + volumes: + ... + ## custom resources + - ./custom/www/src/index.md:/opt/chatmail/www/src/index.md +``` + +4. Перезапустить сервис +```shell +docker compose down +docker compose up -d +``` + +## Фиксирование версии Chatmail +> [!note] +> Это опциональные шаги, их делать требуется только если вас не устраивает что сервис устанавливается каждый раз при запуске + +Поскольку в текущей версии docker chatmail сервис устанавливается каждый раз запуске контейнера, чтобы этого не происходило можно зафиксировать версию контейнера после установки. Делается это следующим образом: + +1. Зафиксировать текущее состояние сконфигурированного контейнера +```shell +docker container commit chatmail configured-chatmail:$(date +'%Y-%m-%d') +docker image ls | grep configured-chatmail +``` + +2. Изменить entrypoint для контейнера в `docker-compose.yaml` на +```yaml +services: + chatmail: + image: + volumes: + ... + ## custom resources + - ./custom/setup_chatmail_docker.sh:/setup_chatmail_docker.sh +``` + +3. Создать файл `./custom/setup_chatmail_docker.sh` с новым файлом конфигурации +```shell +mkdir -p ./custom +cat > ./custom/setup_chatmail_docker.sh << 'EOF' +#!/bin/bash + +set -eo pipefail + +export ENABLE_CERTS_MONITORING="${ENABLE_CERTS_MONITORING:-true}" +export CERTS_MONITORING_TIMEOUT="${CERTS_MONITORING_TIMEOUT:-60}" +export PATH_TO_SSL_CONTAINER="${PATH_TO_SSL_CONTAINER:-/var/lib/acme/live/${MAIL_DOMAIN}}" + +calculate_hash() { + find "$PATH_TO_SSL_CONTAINER" -type f -exec sha1sum {} \; | sort | sha1sum | awk '{print $1}' +} + +monitor_certificates() { + if [ "$ENABLE_CERTS_MONITORING" != "true" ]; then + echo "Certs monitoring disabled." + exit 0 + fi + + current_hash=$(calculate_hash) + previous_hash=$current_hash + + while true; do + current_hash=$(calculate_hash) + if [[ "$current_hash" != "$previous_hash" ]]; then + # TODO: add an option to restart at a specific time interval + echo "[INFO] Certificate's folder hash was changed, restarting nginx, dovecot and postfix services." + systemctl restart nginx.service + systemctl reload dovecot.service + systemctl reload postfix.service + previous_hash=$current_hash + fi + sleep $CERTS_MONITORING_TIMEOUT + done +} + +monitor_certificates & +EOF +``` + +4. Перезапустить сервис +```shell +docker compose down +docker compose up -d +``` diff --git a/www/src/index.md b/www/src/index.md index e167c7402..8613772b3 100644 --- a/www/src/index.md +++ b/www/src/index.md @@ -1,7 +1,8 @@ - -## Dear [Delta Chat](https://get.delta.chat) users and newcomers ... +/// tab | 🇬🇧 English + +## Dear [Delta Chat](https://get.delta.chat) users and newcomers ... {% if config.mail_domain != "nine.testrun.org" %} Welcome to instant, interoperable and [privacy-preserving](privacy.html) messaging :) @@ -23,7 +24,34 @@ you can also **scan this QR code** with Delta Chat: 🐣 **Choose** your Avatar and Name 💬 **Start** chatting with any Delta Chat contacts using [QR invite codes](https://delta.chat/en/help#howtoe2ee) +/// + +/// tab | 🇷🇺 Русский + +## Уважаемые пользователи и новички [Delta Chat](https://get.delta.chat)... {% if config.mail_domain != "nine.testrun.org" %} +Добро пожаловать в мир мгновенного, совместимого и [конфиденциального](privacy.html) обмена сообщениями :) +{% else %} +Вы находитесь на сервере по умолчанию ({{ config.mail_domain }}) +для пользователей Delta Chat. Подробную информацию о том, как он избегает хранения личной информации, +см. в нашей [политике конфиденциальности](privacy.html). +{% endif %} + +Создать чат-профиль на {{config.mail_domain}} + +Если вы открыли эту страницу на устройстве, +где нет приложения Delta Chat, вы можете +**отсканировать этот QR-код** с помощью Delta Chat: + + + + +🐣 **Выберите** аватар и имя + +💬 **Начните** чат с любыми контактами Delta Chat через [QR-приглашения](https://delta.chat/ru/help#howtoe2ee) +/// + +{% if config.is_development_instance == True %}
Note: this is only a temporary development chatmail service
{% endif %} diff --git a/www/src/info.md b/www/src/info.md index 1fd7eb5e3..fc928db43 100644 --- a/www/src/info.md +++ b/www/src/info.md @@ -1,3 +1,6 @@ + + +/// tab | 🇬🇧 English ## More information @@ -41,3 +44,47 @@ This chatmail provider is run by a small voluntary group of devs and sysadmins, who [publically develop chatmail provider setups](https://github.com/deltachat/chatmail). Chatmail setups aim to be very low-maintenance, resource efficient and interoperable with any other standards-compliant e-mail service. +/// + +/// tab | 🇷🇺 Русский + +## Дополнительная информация + +{{ config.mail_domain }} предоставляет малозатратный, ресурсосберегающий и совместимый с другими системами почтовый сервис для всех. За `chatmail` фактически скрывается +обычный почтовый адрес, как и любой другой, но оптимизированный +для использования в чатах, особенно DeltaChat. + +### Ограничения по скорости и хранению + +* Незашифрованные сообщения блокируются для получателей вне + {{config.mail_domain}}, но добавление контакта через [QR-коды приглашения](https://delta.chat/en/help#howtoe2ee) + позволяет свободно обмениваться сообщениями между с ним. + +* Вы можете отправлять до {{ config.max_user_send_per_minute }} сообщений в минуту. + +- Вы можете хранить до [{{ config.max_mailbox_size }} сообщений на сервере](https://delta.chat/en/help#what-happens-if-i-turn-on-delete-old-messages-from-server). + +* Сообщения в любом случае будут удалены с сервера через {{ config.delete_mails_after }} дней после поступления на сервер. + Или раньше, если хранилище превышает допустимый объем. + +### Удаление аккаунта + +Если вы удалите профиль {{ config.mail_domain }} через приложение Delta Chat, +соответствующая учетная запись на сервере и все связанные с ней данные +будут автоматически удалены через {{ config.delete_inactive_users_after }} дней. + +Если вы используете несколько устройств, +вам необходимо удалить профиль чата на каждом из них, +чтобы все данные аккаунта были удалены с сервера. + +Если у вас есть дополнительные вопросы или запросы по поводу удаления аккаунта, +пожалуйста, отправьте сообщение со своей учетной записи на {{ config.privacy_mail }}. + +### Кто операторы? Какое ПО используется? + +Этот chatmail провайдер управляется небольшой группой добровольцев — разработчиков и системных администраторов, +которые [публично разрабатывают инфраструктуру chatmail провайдеров](https://github.com/deltachat/chatmail). +Chatmail стремится быть максимально простыми в обслуживании, ресурсосберегающими и +совместимыми с любым другим почтовым сервисом, соответствующим стандартам. + +/// diff --git a/www/src/main.css b/www/src/main.css index 772b2e9d6..6acf12a32 100644 --- a/www/src/main.css +++ b/www/src/main.css @@ -84,3 +84,57 @@ code { color: white !important; font-weight: bold; } + +.tabbed-set { + position: relative; + display: flex; + flex-wrap: wrap; + margin: 1em 0; + border-radius: 0.1rem; +} + +.tabbed-set > input { + display: none; +} + +.tabbed-set label { + width: auto; + padding: 0.9375em 1.25em 0.78125em; + font-weight: 700; + font-size: 0.84em; + white-space: nowrap; + border-bottom: 0.15rem solid transparent; + border-top-left-radius: 0.1rem; + border-top-right-radius: 0.1rem; + cursor: pointer; + transition: background-color 250ms, color 250ms; +} + +.tabbed-set .tabbed-content { + width: 100%; + display: none; + box-shadow: 0 -.05rem #ddd; +} + +.tabbed-set input { + position: absolute; + opacity: 0; +} + +.tabbed-set input:checked:nth-child(n+1) + label { + color: red; + border-color: red; +} + +@media screen { + .tabbed-set input:nth-child(n+1):checked + label + .tabbed-content { + order: 99; + display: block; + } +} + +@media print { + .tabbed-content { + display: contents; + } +} diff --git a/www/src/page-layout.html b/www/src/page-layout.html index 4fe178eb9..28ffde561 100644 --- a/www/src/page-layout.html +++ b/www/src/page-layout.html @@ -9,7 +9,7 @@ {{ config.mail_domain }} {{ pagename }} - + diff --git a/www/src/privacy.md b/www/src/privacy.md index a66269605..cde7d0303 100644 --- a/www/src/privacy.md +++ b/www/src/privacy.md @@ -1,3 +1,6 @@ + + +/// tab | 🇬🇧 English # Privacy Policy for {{ config.mail_domain }} @@ -267,5 +270,199 @@ as of *October 2024*. Due to the further development of our service and offers or due to changed legal or official requirements, it may become necessary to revise this data protection declaration from time to time. +/// + +/// tab | 🇷🇺 Русский + +# Политика конфиденциальности для {{ config.mail_domain }} + +{% if config.mail_domain == "nine.testrun.org" %} +Добро пожаловать на `{{config.mail_domain}}` — это основной сервер Chatmail для новых пользователей Delta Chat. +Он поддерживается небольшой командой системных администраторов на добровольной основе. +Альтернативные сервера вы можете найти [здесь](https://delta.chat/en/chatmail). +{% endif %} + +## Кратко: Личные данные не запрашиваются и не собираются + +Этот сервер Chatmail не запрашивает и не сохраняет личную информацию. +Серверы Chatmail существуют исключительно для надёжной передачи (временного хранения и доставки) зашифрованных сообщений между устройствами пользователей, использующих мессенджер Delta Chat. + +Технически, Chatmail-сервер можно представить как «маршрутизатор сообщений» с поддержкой сквозного шифрования в масштабе интернета. + +В отличие от классических почтовых сервисов (например, Gmail), +Chatmail-серверы не запрашивают личные данные и не хранят письма постоянно. +Они ближе по устройству к серверам Signal, +однако не используют номера телефонов и могут безопасно и автоматически взаимодействовать как с другими Chatmail-серверами, так и с обычной электронной почтой. + +Отличия от традиционных почтовых серверов: + +- безусловное удаление сообщений через {{ config.delete_mails_after }} дней; +- невозможность отправки незашифрованных сообщений; +- отсутствие хранения IP-адресов; +- IP-адреса не обрабатываются в связке с адресами электронной почты. + +Из-за отсутствия обработки персональных данных +данный сервер, возможно, формально не обязан иметь политику конфиденциальности. + +Тем не менее, ниже приведена юридическая информация +для удобства специалистов по защите данных и юристов, изучающих работу Chatmail. + +--- + +## 1. Название и контактная информация + +Ответственный за обработку ваших персональных данных: + +``` +{{ config.privacy_postal }} +``` + +Эл. почта: {{ config.privacy_mail }} + +Назначен ответственный по защите данных: + +``` +{{ config.privacy_pdo }} +``` + +--- + +## 2. Обработка при использовании чата и электронной почты + +Мы предоставляем сервисы, оптимизированные для работы с приложением [Delta Chat](https://delta.chat), +и обрабатываем только те данные, которые необходимы для настройки и технической реализации доставки сообщений. +Цель обработки — дать пользователям возможность читать, писать, управлять, удалять, отправлять и получать сообщения. + +Для этого мы используем серверное ПО, обеспечивающее передачу сообщений. + +Обрабатываются следующие данные: + +- Исходящие и входящие сообщения (SMTP) временно хранятся до их доставки получателю; +- Сообщения доступны получателю через IMAP до их удаления пользователем или по истечении установленного срока + (*обычно 4–8 недель*); +- Протоколы IMAP и SMTP защищены паролем, уникальным для каждого аккаунта; +- Пользователи могут самостоятельно просматривать или удалять сообщения через любой стандартный IMAP-клиент; +- Также возможно подключение к «службе передачи в реальном времени», + которая устанавливает P2P-соединение между устройствами и позволяет отправлять временные сообщения, + которые *никогда* не сохраняются на сервере — даже в зашифрованном виде. + +### 2.1 Создание аккаунта + +Аккаунт создаётся одним из двух способов: + +- с помощью QR-кода приглашения, + отсканированного через приложение Delta Chat; + +- автоматически, при создании и регистрации аккаунта в {{ config.mail_domain }} через приложение Delta Chat. + +В любом случае, обрабатывается только созданный адрес электронной почты. +Номера телефонов, другие адреса электронной почты или любые другие идентификаторы не требуются. +Правовое основание для обработки — +статья 6 (1) пункт b Общего регламента по защите данных (GDPR), +так как вы заключаете пользовательский договор, пользуясь нашим сервисом. + +### 2.2 Обработка почтовых сообщений + +Кроме того, мы обрабатываем данные, +необходимые для обеспечения стабильной работы инфраструктуры сервера, +доставки сообщений и предотвращения злоупотреблений. + +- Поэтому может потребоваться обработка содержимого и/или метаданных + (например, заголовков писем и технической информации SMTP) во время передачи; + +- Мы храним логи передаваемых сообщений ограниченное время — + они используются для устранения проблем с доставкой и ошибок ПО. + +Также мы вводим ограничения для защиты системы от перегрузок: + +- ограничения скорости (rate limits), +- лимиты на объём хранения, +- ограничения на размер сообщений, +- любые другие меры, необходимые для стабильной работы сервера и предотвращения злоупотреблений. + +Обработка вышеуказанных данных необходима для предоставления сервиса. +Правовое основание — статья 6 (1) пункт b GDPR. +Обработка данных в целях безопасности и предотвращения злоупотреблений основана на статье 6 (1) пункт f GDPR, +и соответствует нашим законным интересам. + +Мы не используем собранные данные для определения вашей личности. + +--- + +## 3. Обработка при посещении сайта + +При посещении нашего сайта браузер вашего устройства +автоматически передаёт определённую информацию на сервер, +где она временно сохраняется в так называемых лог-файлах. +Эти данные автоматически удаляются (обычно через *7 дней*). + +Среди собираемых данных: + +- тип используемого браузера, +- операционная система, +- дата и время доступа, +- страна и IP-адрес, +- запрашиваемый файл или ресурс, +- объём переданных данных, +- статус доступа (успешно, ошибка и т.п.), +- страница, с которой был сделан запрос. + +Хостинг нашего сайта осуществляется внешним провайдером. +Личные данные, собираемые на сайте, хранятся на его серверах. +Провайдер обрабатывает данные строго по нашим инструкциям, +в пределах заключённого договора на обработку данных (ст. 28 GDPR). + +Цели обработки: + +- обеспечение стабильного подключения к сайту; +- удобство использования сайта; +- контроль безопасности и стабильности системы; +- административные цели. + +Правовое основание — статья 6 (1) пункт f GDPR. +Собранные данные не используются для установления вашей личности. + +--- + +## 4. Передача данных + +Мы не сохраняем личные данные, +но письма, ожидающие доставки, могут содержать личную информацию. +Такие данные не передаются третьим лицам, за исключением следующих случаев: + +a) при наличии вашего явного согласия (ст. 6 п.1 п. a GDPR); + +b) если передача необходима для защиты прав, интересов или правовой позиции (ст. 6 п.1 п. f GDPR); + +c) если это требуется по закону (ст. 6 п.1 п. c GDPR); + +d) если это необходимо для исполнения договора с вами (ст. 6 п.1 п. b GDPR); + +e) если обработка осуществляется сервис-провайдером по нашему поручению, + с которым заключён договор (ст. 28 GDPR), + предусматривающий меры безопасности и контроль с нашей стороны. + +--- + +## 5. Права субъектов данных + +Ваши права закреплены в статьях 12–23 GDPR. +Так как сервер не хранит персональные данные — даже в зашифрованном виде — +предоставление информации или подача возражений не требуются. +Удаление данных можно выполнить напрямую через приложение Delta Chat. + +Если у вас есть вопросы или жалобы, напишите нам: +{{ config.privacy_mail }} + +Также вы можете обратиться в надзорный орган по месту вашего проживания, +работы или к органу, ответственному за нашу деятельность: +`{{ config.privacy_supervisor }}`. + +--- +## 6. Актуальность политики конфиденциальности +Настоящая политика действует с *октября 2024 года*. +В случае изменений в услугах или законодательства +она может быть обновлена. +/// From 4a92e505cf63b66f6d0b91f93a44e6d21dc45543 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sun, 17 Aug 2025 13:01:32 +0300 Subject: [PATCH 02/33] Update Changelog https://github.com/chatmail/relay/pull/614#discussion_r2269932560 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c61c9a8ad..a4b251e4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ - Add `--skip-dns-check` argument to `cmdeploy run` command, which disables DNS record checking before installation. ([#614](https://github.com/chatmail/relay/pull/614)) -- Add `--force` argument to `cmdeploy init` command, which recreates the `config.ini` file. +- Add `--force` argument to `cmdeploy init` command, which recreates the `chatmail.ini` file. ([#614](https://github.com/chatmail/relay/pull/614)) - Add startup for `fcgiwrap.service` because sometimes it did not start automatically. From 6425a839ae60bbba056825eba864109511d2794d Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sun, 17 Aug 2025 13:06:55 +0300 Subject: [PATCH 03/33] Fix description for is_development_instance option - https://github.com/chatmail/relay/pull/614#discussion_r2269945334 - https://github.com/chatmail/relay/pull/614#discussion_r2269950555 --- chatmaild/src/chatmaild/ini/chatmail.ini.f | 2 +- cmdeploy/src/cmdeploy/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chatmaild/src/chatmaild/ini/chatmail.ini.f b/chatmaild/src/chatmaild/ini/chatmail.ini.f index 13926745b..427956c2d 100644 --- a/chatmaild/src/chatmaild/ini/chatmail.ini.f +++ b/chatmaild/src/chatmaild/ini/chatmail.ini.f @@ -49,7 +49,7 @@ # Deployment Details # -# if set to "True" on main page will be showed dev banner +# set to "False" to remove the "development instance" banner on the main page. is_development_instance = True # SMTP outgoing filtermail and reinjection diff --git a/cmdeploy/src/cmdeploy/__init__.py b/cmdeploy/src/cmdeploy/__init__.py index 58491fb37..6e1c2c9af 100644 --- a/cmdeploy/src/cmdeploy/__init__.py +++ b/cmdeploy/src/cmdeploy/__init__.py @@ -680,7 +680,7 @@ def deploy_chatmail(config_path: Path, disable_mail: bool) -> None: if host.get_fact(SystemdStatus, services="unbound").get("unbound.service"): process_on_53 = "unbound" if process_on_53 not in (None, "unbound"): - Out().red(f"Can't install unbound: port 53 is occupied by: {process_on_53}") + Out().red(f"Can't install unbound: port 53 is occupied by: {process_on_53}") exit(1) apt.packages( name="Install unbound", From 1c4c7b9665a6f560c1446f0ea08a64f307172f7e Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sun, 17 Aug 2025 13:09:32 +0300 Subject: [PATCH 04/33] revert page-layout logo link - https://github.com/chatmail/relay/pull/614#discussion_r2279697445 --- www/src/page-layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/src/page-layout.html b/www/src/page-layout.html index 28ffde561..4fe178eb9 100644 --- a/www/src/page-layout.html +++ b/www/src/page-layout.html @@ -9,7 +9,7 @@ {{ config.mail_domain }} {{ pagename }} - + From aea6366bb347db422b6120489b2e55fdec8a2e93 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 23 Aug 2025 15:26:00 +0300 Subject: [PATCH 05/33] rename dockerfile https://github.com/chatmail/relay/pull/614#discussion_r2270031966 --- .../{chatmail_server.dockerfile => chatmail_relay.dockerfile} | 0 docker/docker-compose-default.yaml | 2 +- docker/docker-compose-traefik.yaml | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename docker/{chatmail_server.dockerfile => chatmail_relay.dockerfile} (100%) diff --git a/docker/chatmail_server.dockerfile b/docker/chatmail_relay.dockerfile similarity index 100% rename from docker/chatmail_server.dockerfile rename to docker/chatmail_relay.dockerfile diff --git a/docker/docker-compose-default.yaml b/docker/docker-compose-default.yaml index 61e7765f2..cfdc3a556 100644 --- a/docker/docker-compose-default.yaml +++ b/docker/docker-compose-default.yaml @@ -2,7 +2,7 @@ services: chatmail: build: context: ./docker - dockerfile: chatmail_server.dockerfile + dockerfile: chatmail_relay.dockerfile tags: - chatmail-relay:latest image: chatmail-relay:latest diff --git a/docker/docker-compose-traefik.yaml b/docker/docker-compose-traefik.yaml index 1601578f9..22a9cff89 100644 --- a/docker/docker-compose-traefik.yaml +++ b/docker/docker-compose-traefik.yaml @@ -2,7 +2,7 @@ services: chatmail: build: context: ./docker - dockerfile: chatmail_server.dockerfile + dockerfile: chatmail_relay.dockerfile tags: - chatmail-relay:latest image: chatmail-relay:latest From b6dce619bd06a753690374be0c6e5c0082169c5b Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 23 Aug 2025 15:27:30 +0300 Subject: [PATCH 06/33] add port 80 to docker-compose-default https://github.com/chatmail/relay/pull/614#discussion_r2279656441 --- docker/docker-compose-default.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/docker-compose-default.yaml b/docker/docker-compose-default.yaml index cfdc3a556..be29e4a61 100644 --- a/docker/docker-compose-default.yaml +++ b/docker/docker-compose-default.yaml @@ -32,6 +32,7 @@ services: # CERTS_MONITORING_TIMEOUT: 10 # IS_DEVELOPMENT_INSTANCE: "True" ports: + - "80:80" - "25:25" - "587:587" - "143:143" From a6e5b9e0aad9339e76137f5e567460fcc2f78176 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 23 Aug 2025 15:28:44 +0300 Subject: [PATCH 07/33] add 465 port https://github.com/chatmail/relay/pull/614#discussion_r2279707059 --- docker/docker-compose-default.yaml | 3 ++- docker/docker-compose-traefik.yaml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/docker-compose-default.yaml b/docker/docker-compose-default.yaml index be29e4a61..33f5c6d54 100644 --- a/docker/docker-compose-default.yaml +++ b/docker/docker-compose-default.yaml @@ -33,11 +33,12 @@ services: # IS_DEVELOPMENT_INSTANCE: "True" ports: - "80:80" + - "443:443" - "25:25" - "587:587" - "143:143" + - "465:465" - "993:993" - - "443:443" volumes: ## system - /sys/fs/cgroup:/sys/fs/cgroup:rw # required for systemd diff --git a/docker/docker-compose-traefik.yaml b/docker/docker-compose-traefik.yaml index 22a9cff89..09ad25f43 100644 --- a/docker/docker-compose-traefik.yaml +++ b/docker/docker-compose-traefik.yaml @@ -36,6 +36,7 @@ services: - "25:25" - "587:587" - "143:143" + - "465:465" - "993:993" volumes: ## system From a01eebe2db5f436f806deb945ba69b532dd22372 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 23 Aug 2025 15:37:18 +0300 Subject: [PATCH 08/33] add RECREATE_VENV var https://github.com/chatmail/relay/pull/614#discussion_r2279742769 --- docker/docker-compose-default.yaml | 4 ++-- docker/docker-compose-traefik.yaml | 1 + docker/files/setup_chatmail_docker.sh | 4 ++++ docs/DOCKER_INSTALLATION_EN.md | 1 + docs/DOCKER_INSTALLATION_RU.md | 1 + 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docker/docker-compose-default.yaml b/docker/docker-compose-default.yaml index 33f5c6d54..4edfbe555 100644 --- a/docker/docker-compose-default.yaml +++ b/docker/docker-compose-default.yaml @@ -23,8 +23,8 @@ services: MAIL_DOMAIN: CHANGE_KERNEL_SETTINGS: "False" ACME_EMAIL: - - MAX_MESSAGE_SIZE: "50M" + # RECREATE_VENV: "false" + # MAX_MESSAGE_SIZE: "50M" # DEBUG_COMMANDS_ENABLED: "true" # FORCE_REINIT_INI_FILE: "true" # USE_FOREIGN_CERT_MANAGER: "True" diff --git a/docker/docker-compose-traefik.yaml b/docker/docker-compose-traefik.yaml index 09ad25f43..140ffabd0 100644 --- a/docker/docker-compose-traefik.yaml +++ b/docker/docker-compose-traefik.yaml @@ -26,6 +26,7 @@ services: # MAX_MESSAGE_SIZE: "50M" # DEBUG_COMMANDS_ENABLED: "true" # FORCE_REINIT_INI_FILE: "true" + # RECREATE_VENV: "false" USE_FOREIGN_CERT_MANAGER: "true" CHANGE_KERNEL_SETTINGS: "false" PATH_TO_SSL_CONTAINER: $PATH_TO_SSL_CONTAINER diff --git a/docker/files/setup_chatmail_docker.sh b/docker/files/setup_chatmail_docker.sh index 7e1017913..bdff49847 100755 --- a/docker/files/setup_chatmail_docker.sh +++ b/docker/files/setup_chatmail_docker.sh @@ -6,6 +6,7 @@ export ENABLE_CERTS_MONITORING="${ENABLE_CERTS_MONITORING:-true}" export CERTS_MONITORING_TIMEOUT="${CERTS_MONITORING_TIMEOUT:-60}" export PATH_TO_SSL_CONTAINER="${PATH_TO_SSL_CONTAINER:-/var/lib/acme/live/${MAIL_DOMAIN}}" export CHANGE_KERNEL_SETTINGS=${CHANGE_KERNEL_SETTINGS:-"False"} +export RECREATE_VENV=${RECREATE_VENV:-"false"} if [ -z "$MAIL_DOMAIN" ]; then echo "ERROR: Environment variable 'MAIL_DOMAIN' must be set!" >&2 @@ -61,6 +62,9 @@ chown opendkim:opendkim /etc/dkimkeys/opendkim.txt # TODO: Move to debug_commands after git clone is moved to dockerfile. git config --global --add safe.directory /opt/chatmail +if [ "$RECREATE_VENV" == "true" ]; then + rm -rf venv +fi ./scripts/initenv.sh ./scripts/cmdeploy init --config "${INI_FILE}" $INI_CMD_ARGS $MAIL_DOMAIN diff --git a/docs/DOCKER_INSTALLATION_EN.md b/docs/DOCKER_INSTALLATION_EN.md index c6c44bb80..7e91b47e4 100644 --- a/docs/DOCKER_INSTALLATION_EN.md +++ b/docs/DOCKER_INSTALLATION_EN.md @@ -68,6 +68,7 @@ sudo sysctl --system - `DEBUG_COMMANDS_ENABLED` – Run debug commands before installation. (default: `false`) - `FORCE_REINIT_INI_FILE` – Recreate the ini configuration file on startup. (default: `false`) - `USE_FOREIGN_CERT_MANAGER` – Use a third-party certificate manager. (default: `false`) +- `RECREATE_VENV` - Recreate the virtual environment (venv). If set to `true`, the environment will be recreated when the container starts, which will increase the startup time of the service but can help avoid certain errors. (default: `false`) - `INI_FILE` – Path to the ini configuration file. (default: `./chatmail.ini`) - `PATH_TO_SSL_CONTAINER` – Path to where the certificates are stored. (default: `/var/lib/acme/live/${MAIL_DOMAIN}`) - `ENABLE_CERTS_MONITORING` – Enable certificate monitoring if `USE_FOREIGN_CERT_MANAGER=true`. If certificates change, services will be automatically restarted. (default: `false`) diff --git a/docs/DOCKER_INSTALLATION_RU.md b/docs/DOCKER_INSTALLATION_RU.md index 36d1913aa..893c6be31 100644 --- a/docs/DOCKER_INSTALLATION_RU.md +++ b/docs/DOCKER_INSTALLATION_RU.md @@ -60,6 +60,7 @@ sudo sysctl --system - `DEBUG_COMMANDS_ENABLED` - Выполнить debug команды перед установкой. (default: `false`) - `FORCE_REINIT_INI_FILE` - Пересоздавать ini файл конфигурации при запуске. (default: `false`) - `USE_FOREIGN_CERT_MANAGER` - Использовать сторонний менеджер сертификатов. (default: `false`) +- `RECREATE_VENV` - Пересоздать виртуальное окружение (venv). Если выставлено `true`, то окружение будет пересоздано при запуске контейнера, из-за чего включение сервиса займет больше времени, но поможет избежать ряда ошибок. (default: `false`) - `INI_FILE` - путь к ini файлу конфигурации. (default: `./chatmail.ini`) - `PATH_TO_SSL_CONTAINER` - Путь где располагаются сертификаты. (default: `/var/lib/acme/live/${MAIL_DOMAIN}`) - `ENABLE_CERTS_MONITORING` - Включить мониторинг сертификатов, если `USE_FOREIGN_CERT_MANAGER=true`. Если сертфикаты изменятся сервисы будут автоматически перезапущены. (default: `false`) From d545fc8f10e559c111110a3c33defd719e3529ad Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 23 Aug 2025 18:02:45 +0300 Subject: [PATCH 09/33] Add traefik config files https://github.com/chatmail/relay/pull/614#discussion_r2269887232 --- .gitignore | 1 + docker/docker-compose-traefik.yaml | 37 +++++++--- docker/example.env | 3 +- docker/files/entrypoint.sh | 2 + docs/DOCKER_INSTALLATION_EN.md | 99 --------------------------- docs/DOCKER_INSTALLATION_RU.md | 95 ------------------------- traefik/config.yaml | 33 +++++++++ traefik/dynamic-configs/insecure.yaml | 4 ++ traefik/post-hook.sh | 12 ++++ 9 files changed, 82 insertions(+), 204 deletions(-) create mode 100644 traefik/config.yaml create mode 100644 traefik/dynamic-configs/insecure.yaml create mode 100755 traefik/post-hook.sh diff --git a/.gitignore b/.gitignore index c6260e934..ed1cb4511 100644 --- a/.gitignore +++ b/.gitignore @@ -170,3 +170,4 @@ chatmail.zone /custom/ docker-compose.yaml .env +/traefik/data/ diff --git a/docker/docker-compose-traefik.yaml b/docker/docker-compose-traefik.yaml index 140ffabd0..18b8b9a03 100644 --- a/docker/docker-compose-traefik.yaml +++ b/docker/docker-compose-traefik.yaml @@ -69,6 +69,22 @@ services: - traefik.http.routers.chatmail-relay.tls=true - traefik.http.routers.chatmail-relay.tls.certresolver=letsEncrypt + traefik_init: + image: alpine:latest + restart: on-failure + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + working_dir: /app + entrypoint: sh -c ' + touch acme.json && + sudo chown 0:0 ./acme.json && + sudo chmod 600 ./acme.json' + volumes: + - ./traefik/data:/app + traefik: image: traefik:v3.3 container_name: traefik @@ -79,17 +95,20 @@ services: max-size: "10m" max-file: "3" command: - - --configFile=/config.yaml + - "--configFile=/config.yaml" + - "--certificatesresolvers.letsEncrypt.acme.email=${ACME_EMAIL:-my.email@gmail.com}" # ports: # - "80:80" # - "443:443" + network_mode: host + depends_on: + traefik_init: + condition: service_completed_successfully volumes: - /var/run/docker.sock:/var/run/docker.sock - - ./data/traefik/config.yaml:/config.yaml - - ./data/traefik/acme.json:/acme.json - - ./data/traefik/dynamic-configs:/dynamic/conf - - network_mode: host + - ./traefik/config.yaml:/config.yaml + - ./traefik/data/acme.json:/acme.json + - ./traefik/dynamic-configs:/dynamic/conf traefik-certs-dumper: image: ldez/traefik-certs-dumper:v2.10.0 @@ -112,6 +131,6 @@ services: environment: CERTS_DIR: /data/letsencrypt/certs volumes: - - ./data/traefik/letsencrypt:/data/letsencrypt - - ./data/traefik/acme.json:/data/acme.json - - ./data/traefik/post-hook.sh:/post-hook.sh + - ./traefik/data/letsencrypt:/data/letsencrypt + - ./traefik/data/acme.json:/data/acme.json + - ./traefik/post-hook.sh:/post-hook.sh diff --git a/docker/example.env b/docker/example.env index bb63a0559..4d8b78046 100644 --- a/docker/example.env +++ b/docker/example.env @@ -1,4 +1,5 @@ MAIL_DOMAIN="chat.example.com" +ACME_EMAIL="my.email@gmail.com" -PATH_TO_SSL_HOST="/opt/traefik/data/letsencrypt/certs/${MAIL_DOMAIN}" +PATH_TO_SSL_HOST="./traefik/data/letsencrypt/certs/${MAIL_DOMAIN}" PATH_TO_SSL_CONTAINER="/var/lib/acme/live/${MAIL_DOMAIN}" diff --git a/docker/files/entrypoint.sh b/docker/files/entrypoint.sh index b704c2e3a..750c134b4 100755 --- a/docker/files/entrypoint.sh +++ b/docker/files/entrypoint.sh @@ -4,10 +4,12 @@ set -eo pipefail if [ "${USE_FOREIGN_CERT_MANAGER,,}" == "true" ]; then if [ ! -f "$PATH_TO_SSL_CONTAINER/fullchain" ]; then echo "Error: file '$PATH_TO_SSL_CONTAINER/fullchain' does not exist. Exiting..." > /dev/stderr + sleep 2 exit 1 fi if [ ! -f "$PATH_TO_SSL_CONTAINER/privkey" ]; then echo "Error: file '$PATH_TO_SSL_CONTAINER/privkey' does not exist. Exiting..." > /dev/stderr + sleep 2 exit 1 fi fi diff --git a/docs/DOCKER_INSTALLATION_EN.md b/docs/DOCKER_INSTALLATION_EN.md index 7e91b47e4..1d18677b8 100644 --- a/docs/DOCKER_INSTALLATION_EN.md +++ b/docs/DOCKER_INSTALLATION_EN.md @@ -88,105 +88,6 @@ Mandatory variables for deployment via Docker: docker compose build chatmail ``` -
-Additional steps for configuring with traefik - -> [!note] -> If you are using the default installation without traefik – skip these steps and go to step 7 (running docker compose). - -Before starting traefik, configuration files must be prepared; otherwise, it will not start correctly. - -First, run these commands in the console, replacing their values with the correct ones: - -```shell -export YOUR_EMAIL=your_email@gmail.com -mkdir -p "./data/traefik" -cd "./data/traefik" -``` - -1. Create a traefik configuration file: - -```shell -cat > config.yaml << EOF -log: - level: TRACE - -entryPoints: - web: - address: ":80" - http: - redirections: - entryPoint: - to: websecure - permanent: true - websecure: - address: ":443" - -providers: - docker: - endpoint: "unix:///var/run/docker.sock" - exposedByDefault: false - file: - directory: /dynamic/conf - watch: true - -serverstransport: - insecureskipverify: true - -certificatesResolvers: - letsEncrypt: - acme: - email: $YOUR_EMAIL - storage: /acme.json - caServer: "https://acme-v02.api.letsencrypt.org/directory" - tlschallenge: true - httpChallenge: - entryPoint: web -EOF -``` - -2. Create a post-hook script: - -```shell -cat > post-hook.sh << 'EOF' -CERTS_DIR=${CERTS_DIR:-"/data/letsencrypt/certs"} - -for dir in "$CERTS_DIR"/*/; do - cd "$dir" - if [ -f "certificate.crt" ]; then - ln -sf certificate.crt fullchain - fi - if [ -f "privatekey.key" ]; then - ln -sf privatekey.key privkey - fi - cd - -done -EOF -``` - -3. Create the `acme.json` file: - -```shell -touch acme.json -sudo chown 0:0 ./acme.json # required -sudo chmod 600 ./acme.json # required -``` - -4. Create insecure config: - -```shell -mkdir dynamic-configs -cat > ./dynamic-configs/insecure.yaml << 'EOF' -http: - serversTransports: - insecure: - insecureSkipVerify: true -EOF -cd ../.. -``` - -
- 7. Start docker compose and wait for the installation to finish: ```shell diff --git a/docs/DOCKER_INSTALLATION_RU.md b/docs/DOCKER_INSTALLATION_RU.md index 893c6be31..3e142b6da 100644 --- a/docs/DOCKER_INSTALLATION_RU.md +++ b/docs/DOCKER_INSTALLATION_RU.md @@ -78,101 +78,6 @@ sudo sysctl --system docker compose build chatmail ``` -
- -Дополнительные шаги для конфигурации работы с traefik - -> [!note] -> Если вы используете default установку, без использования traefik - пропустите эти шаги и переходите к шагу 7 (запуск docker compose) - -Перед запуском traefik необходимо подготовить файлы конфигурации, иначе он запустится некорректно. - -Сначала выполните эти команды в консоли, заменив значения в них на корректные. -```shell -export YOUR_EMAIL=your_email@gmail.com -mkdir -p "./data/traefik" -cd "./data/traefik" -``` - -1. Создать файл конфигурации traefik -```shell -cat > config.yaml << EOF -log: - level: TRACE - -entryPoints: - web: - address: ":80" - http: - redirections: - entryPoint: - to: websecure - permanent: true - websecure: - address: ":443" - -providers: - docker: - endpoint: "unix:///var/run/docker.sock" - exposedByDefault: false - file: - directory: /dynamic/conf - watch: true - -serverstransport: - insecureskipverify: true - -certificatesResolvers: - letsEncrypt: - acme: - email: $YOUR_EMAIL - storage: /acme.json - caServer: "https://acme-v02.api.letsencrypt.org/directory" - tlschallenge: true - httpChallenge: - entryPoint: web -EOF -``` - -2. Создать post-hook скрипт -```shell -cat > post-hook.sh << 'EOF' -CERTS_DIR=${CERTS_DIR:-"/data/letsencrypt/certs"} - -for dir in "$CERTS_DIR"/*/; do - cd "$dir" - if [ -f "certificate.crt" ]; then - ln -sf certificate.crt fullchain - fi - if [ -f "privatekey.key" ]; then - ln -sf privatekey.key privkey - fi - cd - -done -EOF -``` - -3. Создать `acme.json` файл -```shell -touch acme.json -sudo chown 0:0 ./acme.json # это обязательно -sudo chmod 600 ./acme.json # это обязательно -``` - -4. Создать insecure config -```shell -mkdir dynamic-configs -cat > ./dynamic-configs/insecure.yaml << 'EOF' -http: - serversTransports: - insecure: - insecureSkipVerify: true -EOF -cd ../.. -``` - -
- 7. Запустить docker compose и дождаться завершения установки ```shell docker compose up -d # запуск сервиса diff --git a/traefik/config.yaml b/traefik/config.yaml new file mode 100644 index 000000000..ff55284d4 --- /dev/null +++ b/traefik/config.yaml @@ -0,0 +1,33 @@ +log: + level: TRACE + +entryPoints: + web: + address: ":80" + http: + redirections: + entryPoint: + to: websecure + permanent: true + websecure: + address: ":443" + +providers: + docker: + endpoint: "unix:///var/run/docker.sock" + exposedByDefault: false + file: + directory: /dynamic/conf + watch: true + +serverstransport: + insecureskipverify: true + +certificatesResolvers: + letsEncrypt: + acme: + storage: /acme.json + caServer: "https://acme-v02.api.letsencrypt.org/directory" + tlschallenge: true + httpChallenge: + entryPoint: web diff --git a/traefik/dynamic-configs/insecure.yaml b/traefik/dynamic-configs/insecure.yaml new file mode 100644 index 000000000..acafed2e6 --- /dev/null +++ b/traefik/dynamic-configs/insecure.yaml @@ -0,0 +1,4 @@ +http: + serversTransports: + insecure: + insecureSkipVerify: true diff --git a/traefik/post-hook.sh b/traefik/post-hook.sh new file mode 100755 index 000000000..06667fe58 --- /dev/null +++ b/traefik/post-hook.sh @@ -0,0 +1,12 @@ +CERTS_DIR=${CERTS_DIR:-"/data/letsencrypt/certs"} + +for dir in "$CERTS_DIR"/*/; do + cd "$dir" + if [ -f "certificate.crt" ]; then + ln -sf certificate.crt fullchain + fi + if [ -f "privatekey.key" ]; then + ln -sf privatekey.key privkey + fi + cd - +done From 90374093621ee5ac903fde0897f4719d7d03a50e Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 23 Aug 2025 18:06:53 +0300 Subject: [PATCH 10/33] change "restart nginx" to "reload nginx" https://github.com/chatmail/relay/pull/614#discussion_r2269896158 --- docker/files/setup_chatmail_docker.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/files/setup_chatmail_docker.sh b/docker/files/setup_chatmail_docker.sh index bdff49847..f3fb4d4fd 100755 --- a/docker/files/setup_chatmail_docker.sh +++ b/docker/files/setup_chatmail_docker.sh @@ -36,8 +36,8 @@ monitor_certificates() { current_hash=$(calculate_hash) if [[ "$current_hash" != "$previous_hash" ]]; then # TODO: add an option to restart at a specific time interval - echo "[INFO] Certificate's folder hash was changed, restarting nginx, dovecot and postfix services." - systemctl restart nginx.service + echo "[INFO] Certificate's folder hash was changed, reloading nginx, dovecot and postfix services." + systemctl reload nginx.service systemctl reload dovecot.service systemctl reload postfix.service previous_hash=$current_hash From dc6d8b4cf2f247fe2c9aeb0482a9ddc78ef08a18 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 23 Aug 2025 18:16:33 +0300 Subject: [PATCH 11/33] pass values to `MAIL_DOMAIN` and `ACME_EMAIL` from vars for docker-compose-default https://github.com/chatmail/relay/pull/614#discussion_r2279591922 --- docker/docker-compose-default.yaml | 4 ++-- docs/DOCKER_INSTALLATION_EN.md | 10 +++++----- docs/DOCKER_INSTALLATION_RU.md | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docker/docker-compose-default.yaml b/docker/docker-compose-default.yaml index 4edfbe555..104c2bfa6 100644 --- a/docker/docker-compose-default.yaml +++ b/docker/docker-compose-default.yaml @@ -20,9 +20,9 @@ services: max-size: "10m" max-file: "3" environment: - MAIL_DOMAIN: + MAIL_DOMAIN: $MAIL_DOMAIN CHANGE_KERNEL_SETTINGS: "False" - ACME_EMAIL: + ACME_EMAIL: $ACME_EMAIL # RECREATE_VENV: "false" # MAX_MESSAGE_SIZE: "50M" # DEBUG_COMMANDS_ENABLED: "true" diff --git a/docs/DOCKER_INSTALLATION_EN.md b/docs/DOCKER_INSTALLATION_EN.md index 1d18677b8..0a8beac01 100644 --- a/docs/DOCKER_INSTALLATION_EN.md +++ b/docs/DOCKER_INSTALLATION_EN.md @@ -48,13 +48,15 @@ cp ./docker/docker-compose-default.yaml docker-compose.yaml # cp ./docker/docker-compose-traefik.yaml docker-compose.yaml ``` -2. Copy `./docker/example.env` and rename it to `.env`. This file stores variables used in `docker-compose.yaml`. Required only when installing with traefik; if using the default setup, you can skip this step. +2. Copy `./docker/example.env` and rename it to `.env`. This file stores variables used in `docker-compose.yaml`. ```shell cp ./docker/example.env .env ``` -3. Configure kernel parameters because they cannot be changed inside the container, specifically `fs.inotify.max_user_instances` and `fs.inotify.max_user_watches`. Run the following: +3. Configure environment variables in the `.env` file. These variables are used in the `docker-compose.yaml` file to pass repeated values. + +4. Configure kernel parameters because they cannot be changed inside the container, specifically `fs.inotify.max_user_instances` and `fs.inotify.max_user_watches`. Run the following: ```shell echo "fs.inotify.max_user_instances=65536" | sudo tee -a /etc/sysctl.d/99-inotify.conf @@ -62,7 +64,7 @@ echo "fs.inotify.max_user_watches=65536" | sudo tee -a /etc/sysctl.d/99-inotify. sudo sysctl --system ``` -4. Configure container environment variables. Below is the list of variables used during deployment: +5. Configure container environment variables. Below is the list of variables used during deployment: - `MAIL_DOMAIN` – The domain name of the future server. (required) - `DEBUG_COMMANDS_ENABLED` – Run debug commands before installation. (default: `false`) @@ -80,8 +82,6 @@ Mandatory variables for deployment via Docker: - `CHANGE_KERNEL_SETTINGS` – Change kernel settings (`fs.inotify.max_user_instances` and `fs.inotify.max_user_watches`) on startup. Changing kernel settings inside the container is not possible! (default: `False`) -5. Configure environment variables in the `.env` file. These variables are used in the `docker-compose.yaml` file to pass repeated values. - 6. Build the Docker image: ```shell diff --git a/docs/DOCKER_INSTALLATION_RU.md b/docs/DOCKER_INSTALLATION_RU.md index 3e142b6da..6eeff1307 100644 --- a/docs/DOCKER_INSTALLATION_RU.md +++ b/docs/DOCKER_INSTALLATION_RU.md @@ -43,19 +43,21 @@ cp ./docker/docker-compose-default.yaml docker-compose.yaml # cp ./docker/docker-compose-traefik.yaml docker-compose.yaml ``` -2. Скопировать `./docker/example.env` и переименовать в `.env`. Здесь хранятся переменные, которые используятся в `docker-compose.yaml`. Нужен только для установки совместно с traefik, если используется default - можно пропустить +2. Скопировать `./docker/example.env` и переименовать в `.env`. Здесь хранятся переменные, которые используятся в `docker-compose.yaml`. ```shell cp ./docker/example.env .env ``` -3. Настроить параметры ядра, потому что внутри контейнера их нельзя изменить, а конкретно `fs.inotify.max_user_instances` и `fs.inotify.max_user_watches`. Для этого выполнить следующее: +3. Настроить переменные окружения в `.env` файле. Эти переменные используются в `docker-compose.yaml` файле, чтобы передавать повторяющиеся значения. + +4. Настроить параметры ядра, потому что внутри контейнера их нельзя изменить, а конкретно `fs.inotify.max_user_instances` и `fs.inotify.max_user_watches`. Для этого выполнить следующее: ```shell echo "fs.inotify.max_user_instances=65536" | sudo tee -a /etc/sysctl.d/99-inotify.conf echo "fs.inotify.max_user_watches=65536" | sudo tee -a /etc/sysctl.d/99-inotify.conf sudo sysctl --system ``` -4. Настроить переменные окружения контейнера. Ниже перечислен список переменных учавствующих при развертывании. +5. Настроить переменные окружения контейнера. Ниже перечислен список переменных учавствующих при развертывании. - `MAIL_DOMAIN` - Доменное имя будущего сервера. (required) - `DEBUG_COMMANDS_ENABLED` - Выполнить debug команды перед установкой. (default: `false`) - `FORCE_REINIT_INI_FILE` - Пересоздавать ini файл конфигурации при запуске. (default: `false`) @@ -71,8 +73,6 @@ sudo sysctl --system Ниже перечислены переменные, которые обязательны быть выставлены при развертывании через docker: - `CHANGE_KERNEL_SETTINGS` - Менять настройки ядра (`fs.inotify.max_user_instances` и `fs.inotify.max_user_watches`) при запуске. При запуске в контейнере смена настроек ядра не может быть выполнена! (default: `False`) -5. Настроить переменные окружения в `.env` файле. Эти переменные используются в `docker-compose.yaml` файле, чтобы передавать повторяющиеся значения. - 6. Собрать docker образ ```shell docker compose build chatmail From 4fc672c3c44a60275a80b2bcb4fd9dae5d412cbd Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 23 Aug 2025 21:30:08 +0300 Subject: [PATCH 12/33] Fix bug with attaching certs --- docker/docker-compose-traefik.yaml | 4 ++-- docker/example.env | 4 ++-- docker/files/entrypoint.sh | 8 ++++---- docker/files/setup_chatmail_docker.sh | 4 ++-- docs/DOCKER_INSTALLATION_EN.md | 6 +++--- docs/DOCKER_INSTALLATION_RU.md | 6 +++--- traefik/post-hook.sh | 3 +++ 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/docker/docker-compose-traefik.yaml b/docker/docker-compose-traefik.yaml index 18b8b9a03..08a0de938 100644 --- a/docker/docker-compose-traefik.yaml +++ b/docker/docker-compose-traefik.yaml @@ -29,7 +29,7 @@ services: # RECREATE_VENV: "false" USE_FOREIGN_CERT_MANAGER: "true" CHANGE_KERNEL_SETTINGS: "false" - PATH_TO_SSL_CONTAINER: $PATH_TO_SSL_CONTAINER + PATH_TO_SSL: "${CERTS_ROOT_DIR_CONTAINER}/${MAIL_DOMAIN}" ENABLE_CERTS_MONITORING: "true" # CERTS_MONITORING_TIMEOUT: 60 # IS_DEVELOPMENT_INSTANCE: "true" @@ -43,7 +43,7 @@ services: ## system - /sys/fs/cgroup:/sys/fs/cgroup:rw # required for systemd - ./:/opt/chatmail - - ${PATH_TO_SSL_HOST}:${PATH_TO_SSL_CONTAINER}:ro + - ${CERTS_ROOT_DIR_HOST}:${CERTS_ROOT_DIR_CONTAINER}:ro ## data - ./data/chatmail:/home diff --git a/docker/example.env b/docker/example.env index 4d8b78046..fdaa193b2 100644 --- a/docker/example.env +++ b/docker/example.env @@ -1,5 +1,5 @@ MAIL_DOMAIN="chat.example.com" ACME_EMAIL="my.email@gmail.com" -PATH_TO_SSL_HOST="./traefik/data/letsencrypt/certs/${MAIL_DOMAIN}" -PATH_TO_SSL_CONTAINER="/var/lib/acme/live/${MAIL_DOMAIN}" +CERTS_ROOT_DIR_HOST="./traefik/data/letsencrypt/certs" +CERTS_ROOT_DIR_CONTAINER="/var/lib/acme/live" diff --git a/docker/files/entrypoint.sh b/docker/files/entrypoint.sh index 750c134b4..1f2ea370c 100755 --- a/docker/files/entrypoint.sh +++ b/docker/files/entrypoint.sh @@ -2,13 +2,13 @@ set -eo pipefail if [ "${USE_FOREIGN_CERT_MANAGER,,}" == "true" ]; then - if [ ! -f "$PATH_TO_SSL_CONTAINER/fullchain" ]; then - echo "Error: file '$PATH_TO_SSL_CONTAINER/fullchain' does not exist. Exiting..." > /dev/stderr + if [ ! -f "$PATH_TO_SSL/fullchain" ]; then + echo "Error: file '$PATH_TO_SSL/fullchain' does not exist. Exiting..." > /dev/stderr sleep 2 exit 1 fi - if [ ! -f "$PATH_TO_SSL_CONTAINER/privkey" ]; then - echo "Error: file '$PATH_TO_SSL_CONTAINER/privkey' does not exist. Exiting..." > /dev/stderr + if [ ! -f "$PATH_TO_SSL/privkey" ]; then + echo "Error: file '$PATH_TO_SSL/privkey' does not exist. Exiting..." > /dev/stderr sleep 2 exit 1 fi diff --git a/docker/files/setup_chatmail_docker.sh b/docker/files/setup_chatmail_docker.sh index f3fb4d4fd..50f1e4ca3 100755 --- a/docker/files/setup_chatmail_docker.sh +++ b/docker/files/setup_chatmail_docker.sh @@ -4,7 +4,7 @@ set -eo pipefail export INI_FILE="${INI_FILE:-chatmail.ini}" export ENABLE_CERTS_MONITORING="${ENABLE_CERTS_MONITORING:-true}" export CERTS_MONITORING_TIMEOUT="${CERTS_MONITORING_TIMEOUT:-60}" -export PATH_TO_SSL_CONTAINER="${PATH_TO_SSL_CONTAINER:-/var/lib/acme/live/${MAIL_DOMAIN}}" +export PATH_TO_SSL="${PATH_TO_SSL:-/var/lib/acme/live/${MAIL_DOMAIN}}" export CHANGE_KERNEL_SETTINGS=${CHANGE_KERNEL_SETTINGS:-"False"} export RECREATE_VENV=${RECREATE_VENV:-"false"} @@ -20,7 +20,7 @@ debug_commands() { } calculate_hash() { - find "$PATH_TO_SSL_CONTAINER" -type f -exec sha1sum {} \; | sort | sha1sum | awk '{print $1}' + find "$PATH_TO_SSL" -type f -exec sha1sum {} \; | sort | sha1sum | awk '{print $1}' } monitor_certificates() { diff --git a/docs/DOCKER_INSTALLATION_EN.md b/docs/DOCKER_INSTALLATION_EN.md index 0a8beac01..3dfff0363 100644 --- a/docs/DOCKER_INSTALLATION_EN.md +++ b/docs/DOCKER_INSTALLATION_EN.md @@ -72,7 +72,7 @@ sudo sysctl --system - `USE_FOREIGN_CERT_MANAGER` – Use a third-party certificate manager. (default: `false`) - `RECREATE_VENV` - Recreate the virtual environment (venv). If set to `true`, the environment will be recreated when the container starts, which will increase the startup time of the service but can help avoid certain errors. (default: `false`) - `INI_FILE` – Path to the ini configuration file. (default: `./chatmail.ini`) -- `PATH_TO_SSL_CONTAINER` – Path to where the certificates are stored. (default: `/var/lib/acme/live/${MAIL_DOMAIN}`) +- `PATH_TO_SSL` – Path to where the certificates are stored. (default: `/var/lib/acme/live/${MAIL_DOMAIN}`) - `ENABLE_CERTS_MONITORING` – Enable certificate monitoring if `USE_FOREIGN_CERT_MANAGER=true`. If certificates change, services will be automatically restarted. (default: `false`) - `CERTS_MONITORING_TIMEOUT` – Interval in seconds to check if certificates have changed. (default: `'60'`) @@ -171,10 +171,10 @@ set -eo pipefail export ENABLE_CERTS_MONITORING="${ENABLE_CERTS_MONITORING:-true}" export CERTS_MONITORING_TIMEOUT="${CERTS_MONITORING_TIMEOUT:-60}" -export PATH_TO_SSL_CONTAINER="${PATH_TO_SSL_CONTAINER:-/var/lib/acme/live/${MAIL_DOMAIN}}" +export PATH_TO_SSL="${PATH_TO_SSL:-/var/lib/acme/live/${MAIL_DOMAIN}}" calculate_hash() { - find "$PATH_TO_SSL_CONTAINER" -type f -exec sha1sum {} \; | sort | sha1sum | awk '{print $1}' + find "$PATH_TO_SSL" -type f -exec sha1sum {} \; | sort | sha1sum | awk '{print $1}' } monitor_certificates() { diff --git a/docs/DOCKER_INSTALLATION_RU.md b/docs/DOCKER_INSTALLATION_RU.md index 6eeff1307..e523a2aa9 100644 --- a/docs/DOCKER_INSTALLATION_RU.md +++ b/docs/DOCKER_INSTALLATION_RU.md @@ -64,7 +64,7 @@ sudo sysctl --system - `USE_FOREIGN_CERT_MANAGER` - Использовать сторонний менеджер сертификатов. (default: `false`) - `RECREATE_VENV` - Пересоздать виртуальное окружение (venv). Если выставлено `true`, то окружение будет пересоздано при запуске контейнера, из-за чего включение сервиса займет больше времени, но поможет избежать ряда ошибок. (default: `false`) - `INI_FILE` - путь к ini файлу конфигурации. (default: `./chatmail.ini`) -- `PATH_TO_SSL_CONTAINER` - Путь где располагаются сертификаты. (default: `/var/lib/acme/live/${MAIL_DOMAIN}`) +- `PATH_TO_SSL` - Путь где располагаются сертификаты. (default: `/var/lib/acme/live/${MAIL_DOMAIN}`) - `ENABLE_CERTS_MONITORING` - Включить мониторинг сертификатов, если `USE_FOREIGN_CERT_MANAGER=true`. Если сертфикаты изменятся сервисы будут автоматически перезапущены. (default: `false`) - `CERTS_MONITORING_TIMEOUT` - Раз во сколько секунд проверять что изменились сертификаты. (default: `'60'`) @@ -150,10 +150,10 @@ set -eo pipefail export ENABLE_CERTS_MONITORING="${ENABLE_CERTS_MONITORING:-true}" export CERTS_MONITORING_TIMEOUT="${CERTS_MONITORING_TIMEOUT:-60}" -export PATH_TO_SSL_CONTAINER="${PATH_TO_SSL_CONTAINER:-/var/lib/acme/live/${MAIL_DOMAIN}}" +export PATH_TO_SSL="${PATH_TO_SSL:-/var/lib/acme/live/${MAIL_DOMAIN}}" calculate_hash() { - find "$PATH_TO_SSL_CONTAINER" -type f -exec sha1sum {} \; | sort | sha1sum | awk '{print $1}' + find "$PATH_TO_SSL" -type f -exec sha1sum {} \; | sort | sha1sum | awk '{print $1}' } monitor_certificates() { diff --git a/traefik/post-hook.sh b/traefik/post-hook.sh index 06667fe58..377e00fc7 100755 --- a/traefik/post-hook.sh +++ b/traefik/post-hook.sh @@ -1,6 +1,9 @@ CERTS_DIR=${CERTS_DIR:-"/data/letsencrypt/certs"} +echo "CERTS_DIR: $CERTS_DIR" + for dir in "$CERTS_DIR"/*/; do + echo "Processing: $dir" cd "$dir" if [ -f "certificate.crt" ]; then ln -sf certificate.crt fullchain From 4c42d0f18642992b34c70ca422fb8a3cb06618ae Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 23 Aug 2025 21:30:26 +0300 Subject: [PATCH 13/33] fix for lint test --- chatmaild/src/chatmaild/config.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/chatmaild/src/chatmaild/config.py b/chatmaild/src/chatmaild/config.py index ab9d77ac2..09302d31e 100644 --- a/chatmaild/src/chatmaild/config.py +++ b/chatmaild/src/chatmaild/config.py @@ -33,7 +33,9 @@ def __init__(self, inipath, params): self.password_min_length = int(params["password_min_length"]) self.passthrough_senders = params["passthrough_senders"].split() self.passthrough_recipients = params["passthrough_recipients"].split() - self.is_development_instance = params.get("is_development_instance", "true").lower() == "true" + self.is_development_instance = ( + params.get("is_development_instance", "true").lower() == "true" + ) self.filtermail_smtp_port = int(params["filtermail_smtp_port"]) self.filtermail_smtp_port_incoming = int( params["filtermail_smtp_port_incoming"] @@ -44,9 +46,13 @@ def __init__(self, inipath, params): ) self.mtail_address = params.get("mtail_address") self.disable_ipv6 = params.get("disable_ipv6", "false").lower() == "true" - self.use_foreign_cert_manager = params.get("use_foreign_cert_manager", "false").lower() == "true" + self.use_foreign_cert_manager = ( + params.get("use_foreign_cert_manager", "false").lower() == "true" + ) self.acme_email = params["acme_email"] - self.change_kernel_settings = params.get("change_kernel_settings", "true").lower() == "true" + self.change_kernel_settings = ( + params.get("change_kernel_settings", "true").lower() == "true" + ) self.fs_inotify_max_user_instances_and_watchers = int( params["fs_inotify_max_user_instances_and_watchers"] ) From 87615b62d6516b00e45457f89d8aa5cf87429fb7 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 23 Aug 2025 21:36:16 +0300 Subject: [PATCH 14/33] fix docs - nginx "restart" to "reload" https://github.com/chatmail/relay/pull/614#discussion_r2269896158 --- docs/DOCKER_INSTALLATION_EN.md | 4 ++-- docs/DOCKER_INSTALLATION_RU.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/DOCKER_INSTALLATION_EN.md b/docs/DOCKER_INSTALLATION_EN.md index 3dfff0363..d233beba8 100644 --- a/docs/DOCKER_INSTALLATION_EN.md +++ b/docs/DOCKER_INSTALLATION_EN.md @@ -190,8 +190,8 @@ monitor_certificates() { current_hash=$(calculate_hash) if [[ "$current_hash" != "$previous_hash" ]]; then # TODO: add an option to restart at a specific time interval - echo "[INFO] Certificate's folder hash was changed, restarting nginx, dovecot and postfix services." - systemctl restart nginx.service + echo "[INFO] Certificate's folder hash was changed, reloading nginx, dovecot and postfix services." + systemctl reload nginx.service systemctl reload dovecot.service systemctl reload postfix.service previous_hash=$current_hash diff --git a/docs/DOCKER_INSTALLATION_RU.md b/docs/DOCKER_INSTALLATION_RU.md index e523a2aa9..48939bcd9 100644 --- a/docs/DOCKER_INSTALLATION_RU.md +++ b/docs/DOCKER_INSTALLATION_RU.md @@ -169,8 +169,8 @@ monitor_certificates() { current_hash=$(calculate_hash) if [[ "$current_hash" != "$previous_hash" ]]; then # TODO: add an option to restart at a specific time interval - echo "[INFO] Certificate's folder hash was changed, restarting nginx, dovecot and postfix services." - systemctl restart nginx.service + echo "[INFO] Certificate's folder hash was changed, reloading nginx, dovecot and postfix services." + systemctl reload nginx.service systemctl reload dovecot.service systemctl reload postfix.service previous_hash=$current_hash From 1b3f41938407834aa0b2e5ee268028f746eb587a Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 23 Aug 2025 22:47:32 +0300 Subject: [PATCH 15/33] Delete ssh connection from docker installation - https://github.com/chatmail/relay/pull/614#discussion_r2269986372 - https://github.com/chatmail/relay/pull/614#discussion_r2269991175 - https://github.com/chatmail/relay/pull/614#discussion_r2269995037 - https://github.com/chatmail/relay/pull/614#discussion_r2270004922 --- cmdeploy/src/cmdeploy/cmdeploy.py | 7 +++++++ docker/chatmail_relay.dockerfile | 19 ------------------- docker/files/setup_chatmail_docker.sh | 2 +- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/cmdeploy/src/cmdeploy/cmdeploy.py b/cmdeploy/src/cmdeploy/cmdeploy.py index 78b6ad029..2eea65468 100644 --- a/cmdeploy/src/cmdeploy/cmdeploy.py +++ b/cmdeploy/src/cmdeploy/cmdeploy.py @@ -99,7 +99,11 @@ def run_cmd(args, out): deploy_path = importlib.resources.files(__package__).joinpath("deploy.py").resolve() pyinf = "pyinfra --dry" if args.dry_run else "pyinfra" ssh_host = args.config.mail_domain if not args.ssh_host else args.ssh_host + cmd = f"{pyinf} --ssh-user root {ssh_host} {deploy_path} -y" + if sshexec == "localhost": + cmd = f"{pyinf} @local {deploy_path} -y" + if version.parse(pyinfra.__version__) < version.parse("3"): out.red("Please re-run scripts/initenv.sh to update pyinfra to version 3.") return 1 @@ -362,6 +366,9 @@ def main(args=None): def get_sshexec(): host = args.ssh_host if hasattr(args, "ssh_host") and args.ssh_host else args.config.mail_domain + if host in [ "@local", "localhost" ]: + return "localhost" + print(f"[ssh] login to {host}") return SSHExec(host, verbose=args.verbose) diff --git a/docker/chatmail_relay.dockerfile b/docker/chatmail_relay.dockerfile index 72c4a042c..3ec5f81e5 100644 --- a/docker/chatmail_relay.dockerfile +++ b/docker/chatmail_relay.dockerfile @@ -18,8 +18,6 @@ RUN echo 'APT::Install-Recommends "0";' > /etc/apt/apt.conf.d/01norecommend && \ RUN apt-get update && \ apt-get install -y \ - openssh-client \ - openssh-server \ git \ python3 \ python3-venv \ @@ -54,23 +52,6 @@ RUN apt-get update && \ done \ && rm -rf /var/lib/apt/lists/* -RUN systemctl enable \ - ssh \ - fcgiwrap - -RUN sed -i 's/^#PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config && \ - sed -i 's/^#PermitRootLogin .*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config && \ - ssh-keygen -P "" -t rsa -b 2048 -f /root/.ssh/id_rsa && \ - mkdir -p /root/.ssh && \ - cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys && \ - SSH_USER_CONFIG="/root/.ssh/config" && \ - echo "Host localhost" > "$SSH_USER_CONFIG" && \ - echo " HostName localhost" >> "$SSH_USER_CONFIG" && \ - echo " User root" >> "$SSH_USER_CONFIG" && \ - echo " StrictHostKeyChecking no" >> "$SSH_USER_CONFIG" && \ - echo " UserKnownHostsFile /dev/null" >> "$SSH_USER_CONFIG" - ## TODO: deny access for all insteed root form 127.0.0.1 https://unix.stackexchange.com/a/406264 - WORKDIR /opt/chatmail ARG SETUP_CHATMAIL_SERVICE_PATH=/lib/systemd/system/setup_chatmail.service diff --git a/docker/files/setup_chatmail_docker.sh b/docker/files/setup_chatmail_docker.sh index 50f1e4ca3..64d7af360 100755 --- a/docker/files/setup_chatmail_docker.sh +++ b/docker/files/setup_chatmail_docker.sh @@ -70,7 +70,7 @@ fi ./scripts/cmdeploy init --config "${INI_FILE}" $INI_CMD_ARGS $MAIL_DOMAIN bash /update_ini.sh -./scripts/cmdeploy run --ssh-host localhost --skip-dns-check +./scripts/cmdeploy run --ssh-host @local --skip-dns-check echo "ForwardToConsole=yes" >> /etc/systemd/journald.conf systemctl restart systemd-journald From d5329fadc0613dbb4507f7b610dc34a50e6e410f Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sun, 24 Aug 2025 16:14:45 +0300 Subject: [PATCH 16/33] Fix issue with acmetool - https://github.com/chatmail/relay/pull/614#discussion_r2279630626 --- docker/docker-compose-default.yaml | 1 - docker/files/entrypoint.sh | 2 ++ docs/DOCKER_INSTALLATION_EN.md | 1 - docs/DOCKER_INSTALLATION_RU.md | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docker/docker-compose-default.yaml b/docker/docker-compose-default.yaml index 104c2bfa6..224ac9380 100644 --- a/docker/docker-compose-default.yaml +++ b/docker/docker-compose-default.yaml @@ -43,7 +43,6 @@ services: ## system - /sys/fs/cgroup:/sys/fs/cgroup:rw # required for systemd - ./:/opt/chatmail - - ./data/acme:/var/lib/acme ## data - ./data/chatmail:/home diff --git a/docker/files/entrypoint.sh b/docker/files/entrypoint.sh index 1f2ea370c..76d5d408a 100755 --- a/docker/files/entrypoint.sh +++ b/docker/files/entrypoint.sh @@ -1,6 +1,8 @@ #!/bin/bash set -eo pipefail +unlink /etc/nginx/sites-enabled/default + if [ "${USE_FOREIGN_CERT_MANAGER,,}" == "true" ]; then if [ ! -f "$PATH_TO_SSL/fullchain" ]; then echo "Error: file '$PATH_TO_SSL/fullchain' does not exist. Exiting..." > /dev/stderr diff --git a/docs/DOCKER_INSTALLATION_EN.md b/docs/DOCKER_INSTALLATION_EN.md index d233beba8..039aead35 100644 --- a/docs/DOCKER_INSTALLATION_EN.md +++ b/docs/DOCKER_INSTALLATION_EN.md @@ -1,6 +1,5 @@ # Known issues and limitations -- Installation using acmetool (`docker-compose-default.yaml`) may NOT work. In this case, use installation via traefik (`docker-compose-traefik.yaml`). Personally, during my tests, I encountered the error `could not install DNS challenge, no hooks succeeded;`, which I was unable to fix. - Chatmail will be reinstalled every time the container is started (longer the first time, faster on subsequent starts). This is how the original installer works because it wasn’t designed for Docker. At the end of the documentation, there’s a [proposed solution](#locking-the-chatmail-version). - Requires cgroups v2 configured in the system. Operation with cgroups v1 has not been tested. - Yes, of course, using systemd inside a container is a hack, and it would be better to split it into several services, but since this is an MVP, it turned out to be easier to do it this way initially than to rewrite the entire deployment system. diff --git a/docs/DOCKER_INSTALLATION_RU.md b/docs/DOCKER_INSTALLATION_RU.md index 48939bcd9..31bd814df 100644 --- a/docs/DOCKER_INSTALLATION_RU.md +++ b/docs/DOCKER_INSTALLATION_RU.md @@ -1,5 +1,4 @@ # Известные проблемы и ограничения -- Установка с помощью acmetool (`docker-compose-default.yaml`) может НЕ работать. В таком случае используйте установку через traefik (`docker-compose-traefik.yaml`). Лично у меня при тестах ошибка была `could not install DNS challenge, no hooks succeeded;`, которую исправить не удалось. - Chatmail будет переустановлен при каждом запуске контейнера (при первом - долго, при последующих быстрее). Так устроен изначальный установщик, потому что он не был заточен под docker. В конце документации [представлено](#фиксирование-версии-chatmail) возможное решение - Требуется настроенный в системе cgroups v2. Работа с cgroups v1 не тестировалась. - Да, понятно дело что systemd использовать в контейнере костыль и надо это всё разнести на несколько сервисов, но это MVP и в первом приближении оказалось сделать проще так, чем переписывать всю систему развертывания. From 5dcb002bc659ff317b538aeb3524d1f2bb84869a Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Mon, 25 Aug 2025 22:03:12 +0300 Subject: [PATCH 17/33] delete default value for ACME_EMAIL - https://github.com/chatmail/relay/pull/614#discussion_r2297720896 --- docker/docker-compose-traefik.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-compose-traefik.yaml b/docker/docker-compose-traefik.yaml index 08a0de938..d149e3183 100644 --- a/docker/docker-compose-traefik.yaml +++ b/docker/docker-compose-traefik.yaml @@ -96,7 +96,7 @@ services: max-file: "3" command: - "--configFile=/config.yaml" - - "--certificatesresolvers.letsEncrypt.acme.email=${ACME_EMAIL:-my.email@gmail.com}" + - "--certificatesresolvers.letsEncrypt.acme.email=${ACME_EMAIL}" # ports: # - "80:80" # - "443:443" From f027afdd28b25fd5df58f9ca86f26e9d1c1ca49a Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Mon, 25 Aug 2025 22:04:36 +0300 Subject: [PATCH 18/33] delete sudo from traefik init container cmd - https://github.com/chatmail/relay/pull/614#discussion_r2297818856 --- docker/docker-compose-traefik.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/docker-compose-traefik.yaml b/docker/docker-compose-traefik.yaml index d149e3183..108ab0d69 100644 --- a/docker/docker-compose-traefik.yaml +++ b/docker/docker-compose-traefik.yaml @@ -80,8 +80,8 @@ services: working_dir: /app entrypoint: sh -c ' touch acme.json && - sudo chown 0:0 ./acme.json && - sudo chmod 600 ./acme.json' + chown 0:0 ./acme.json && + chmod 600 ./acme.json' volumes: - ./traefik/data:/app From e1ca74ef9fbee2e9977d3674ef45e5fd5be4ec31 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Mon, 25 Aug 2025 22:07:40 +0300 Subject: [PATCH 19/33] fix unlink if default nginx conf is not exist - https://github.com/chatmail/relay/pull/614#discussion_r2297828830 --- docker/files/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/files/entrypoint.sh b/docker/files/entrypoint.sh index 76d5d408a..00efc2195 100755 --- a/docker/files/entrypoint.sh +++ b/docker/files/entrypoint.sh @@ -1,7 +1,7 @@ #!/bin/bash set -eo pipefail -unlink /etc/nginx/sites-enabled/default +unlink /etc/nginx/sites-enabled/default || true if [ "${USE_FOREIGN_CERT_MANAGER,,}" == "true" ]; then if [ ! -f "$PATH_TO_SSL/fullchain" ]; then From c372c55c8876d91e0f40cf5d6ca907019e795f87 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Mon, 25 Aug 2025 22:09:12 +0300 Subject: [PATCH 20/33] try to fix tests - https://github.com/chatmail/relay/pull/614#discussion_r2279758306 --- cmdeploy/src/cmdeploy/tests/test_cmdeploy.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py b/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py index 3084c8ec6..e291c9713 100644 --- a/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py +++ b/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py @@ -23,7 +23,13 @@ def test_parser(self, capsys): def test_init_not_overwrite(self, capsys): assert main(["init", "chat.example.org"]) == 0 - capsys.readouterr() - assert main(["init", "chat.example.org"]) == 1 + out, err = capsys.readouterr() + assert "created config file" in out.lower() + + assert main(["init", "chat.example.org"]) == 0 out, err = capsys.readouterr() assert "path exists" in out.lower() + + assert main(["init", "chat.example.org", "--force"]) == 0 + out, err = capsys.readouterr() + assert "deleting config file" in out.lower() From 929383df88648fab085e1768c10c7fd2d256ecc2 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Mon, 25 Aug 2025 22:14:32 +0300 Subject: [PATCH 21/33] fix docs; revert tests - https://github.com/chatmail/relay/pull/614#discussion_r2297774600 --- cmdeploy/src/cmdeploy/tests/test_cmdeploy.py | 10 ++-------- docs/DOCKER_INSTALLATION_EN.md | 2 +- docs/DOCKER_INSTALLATION_RU.md | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py b/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py index e291c9713..3084c8ec6 100644 --- a/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py +++ b/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py @@ -23,13 +23,7 @@ def test_parser(self, capsys): def test_init_not_overwrite(self, capsys): assert main(["init", "chat.example.org"]) == 0 - out, err = capsys.readouterr() - assert "created config file" in out.lower() - - assert main(["init", "chat.example.org"]) == 0 + capsys.readouterr() + assert main(["init", "chat.example.org"]) == 1 out, err = capsys.readouterr() assert "path exists" in out.lower() - - assert main(["init", "chat.example.org", "--force"]) == 0 - out, err = capsys.readouterr() - assert "deleting config file" in out.lower() diff --git a/docs/DOCKER_INSTALLATION_EN.md b/docs/DOCKER_INSTALLATION_EN.md index 039aead35..5ce7cc441 100644 --- a/docs/DOCKER_INSTALLATION_EN.md +++ b/docs/DOCKER_INSTALLATION_EN.md @@ -84,7 +84,7 @@ Mandatory variables for deployment via Docker: 6. Build the Docker image: ```shell -docker compose build chatmail +docker compose build ``` 7. Start docker compose and wait for the installation to finish: diff --git a/docs/DOCKER_INSTALLATION_RU.md b/docs/DOCKER_INSTALLATION_RU.md index 31bd814df..7051a11f1 100644 --- a/docs/DOCKER_INSTALLATION_RU.md +++ b/docs/DOCKER_INSTALLATION_RU.md @@ -74,7 +74,7 @@ sudo sysctl --system 6. Собрать docker образ ```shell -docker compose build chatmail +docker compose build ``` 7. Запустить docker compose и дождаться завершения установки From 910eeea159453e4a4a411f7ae9ef27f2eeb21c41 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Tue, 26 Aug 2025 21:04:03 +0300 Subject: [PATCH 22/33] Fix colored output file; return original exit code --- cmdeploy/src/cmdeploy/cmdeploy.py | 6 +++--- cmdeploy/src/cmdeploy/tests/test_cmdeploy.py | 8 +++++++- docker/files/setup_chatmail_docker.sh | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cmdeploy/src/cmdeploy/cmdeploy.py b/cmdeploy/src/cmdeploy/cmdeploy.py index 2eea65468..80ff88926 100644 --- a/cmdeploy/src/cmdeploy/cmdeploy.py +++ b/cmdeploy/src/cmdeploy/cmdeploy.py @@ -47,7 +47,7 @@ def init_cmd(args, out): if args.inipath.exists(): if not args.recreate_ini: out.green(f"[WARNING] Path exists, not modifying: {inipath}") - return 0 + return 1 ### need research - can we set return code as zero? else: out.yellow(f"[WARNING] Force argument was provided, deleting config file: {inipath}") inipath.unlink() @@ -273,10 +273,10 @@ class Out: def red(self, msg, file=sys.stderr): print(colored(msg, "red"), file=file) - def green(self, msg, file=sys.stderr): + def green(self, msg, file=sys.stdout): print(colored(msg, "green"), file=file) - def yellow(self, msg, file=sys.stderr): + def yellow(self, msg, file=sys.stdout): print(colored(msg, "yellow"), file=file) def __call__(self, msg, red=False, green=False, yellow=False, file=sys.stdout): diff --git a/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py b/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py index 3084c8ec6..dfa03d8ca 100644 --- a/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py +++ b/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py @@ -23,7 +23,13 @@ def test_parser(self, capsys): def test_init_not_overwrite(self, capsys): assert main(["init", "chat.example.org"]) == 0 - capsys.readouterr() + out, err = capsys.readouterr() + assert "created config file" in out.lower() + assert main(["init", "chat.example.org"]) == 1 out, err = capsys.readouterr() assert "path exists" in out.lower() + + assert main(["init", "chat.example.org", "--force"]) == 0 + out, err = capsys.readouterr() + assert "deleting config file" in out.lower() diff --git a/docker/files/setup_chatmail_docker.sh b/docker/files/setup_chatmail_docker.sh index 64d7af360..7f40c4a35 100755 --- a/docker/files/setup_chatmail_docker.sh +++ b/docker/files/setup_chatmail_docker.sh @@ -67,7 +67,7 @@ if [ "$RECREATE_VENV" == "true" ]; then fi ./scripts/initenv.sh -./scripts/cmdeploy init --config "${INI_FILE}" $INI_CMD_ARGS $MAIL_DOMAIN +./scripts/cmdeploy init --config "${INI_FILE}" $INI_CMD_ARGS $MAIL_DOMAIN || true bash /update_ini.sh ./scripts/cmdeploy run --ssh-host @local --skip-dns-check From a1301fa63e45e942fe71702896b2b935a459fad3 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Tue, 26 Aug 2025 22:04:17 +0300 Subject: [PATCH 23/33] add a workaround to pass the test_init_not_overwrite test --- cmdeploy/src/cmdeploy/cmdeploy.py | 8 ++ docker/files/supervisord.conf | 177 ++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 docker/files/supervisord.conf diff --git a/cmdeploy/src/cmdeploy/cmdeploy.py b/cmdeploy/src/cmdeploy/cmdeploy.py index 80ff88926..d7bfbdcd8 100644 --- a/cmdeploy/src/cmdeploy/cmdeploy.py +++ b/cmdeploy/src/cmdeploy/cmdeploy.py @@ -25,6 +25,8 @@ # cmdeploy sub commands and options # +def is_pytest(): + return "PYTEST_CURRENT_TEST" in os.environ def init_cmd_options(parser): parser.add_argument( @@ -375,6 +377,12 @@ def get_sshexec(): args.get_sshexec = get_sshexec out = Out() + if is_pytest(): ## issue: https://github.com/chatmail/relay/issues/622 + out.green = print + out.red = print + out.yellow = print + out.__call__ = print + kwargs = {} if args.func.__name__ not in ("init_cmd", "fmt_cmd"): if not args.inipath.exists(): diff --git a/docker/files/supervisord.conf b/docker/files/supervisord.conf new file mode 100644 index 000000000..3e2c5fb81 --- /dev/null +++ b/docker/files/supervisord.conf @@ -0,0 +1,177 @@ +[supervisord] +nodaemon=true +logfile=/var/log/supervisor/supervisord.log + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; acmetool-redirector.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:acmetool-redirector] +command=/usr/bin/acmetool redirector --service.uid=daemon +autostart=false +autorestart=true +startsecs=5 +stdout_logfile=/var/log/supervisor/acmetool-redirector.log +stderr_logfile=/var/log/supervisor/acmetool-redirector.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; chatmail-metadata.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:chatmail-metadata] +command=/usr/local/lib/chatmaild/venv/bin/chatmail-metadata /run/chatmail-metadata/metadata.socket /usr/local/lib/chatmaild/chatmail.ini +user=vmail +umask=0077 +autostart=false +autorestart=true +stdout_logfile=/var/log/supervisor/chatmail-metadata.log +stderr_logfile=/var/log/supervisor/chatmail-metadata.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; cron.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:cron] +command=/usr/sbin/cron -f $EXTRA_OPTS +environment=EXTRA_OPTS="" +autostart=true +autorestart=true +stdout_logfile=/var/log/supervisor/cron.log +stderr_logfile=/var/log/supervisor/cron.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; dbus.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:dbus] +command=/usr/bin/dbus-daemon --system --nofork --nopidfile +autostart=false +autorestart=true +stdout_logfile=/var/log/supervisor/dbus.log +stderr_logfile=/var/log/supervisor/dbus.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; doveauth.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:doveauth] +command=/usr/local/lib/chatmaild/venv/bin/doveauth /run/doveauth/doveauth.socket /usr/local/lib/chatmaild/chatmail.ini +user=vmail +umask=0077 +autostart=false +autorestart=true +stdout_logfile=/var/log/supervisor/doveauth.log +stderr_logfile=/var/log/supervisor/doveauth.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; dovecot.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:dovecot] +command=/usr/sbin/dovecot -F +autostart=true +autorestart=true +stdout_logfile=/var/log/supervisor/dovecot.log +stderr_logfile=/var/log/supervisor/dovecot.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; echobot.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:echobot] +command=/usr/local/lib/chatmaild/venv/bin/echobot /usr/local/lib/chatmaild/chatmail.ini +user=echobot +directory=/var/lib/echobot +umask=0077 +autostart=false +autorestart=true +stdout_logfile=/var/log/supervisor/echobot.log +stderr_logfile=/var/log/supervisor/echobot.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; fcgiwrap.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:fcgiwrap] +command=/usr/sbin/fcgiwrap -f +user=www-data +autostart=false +autorestart=true +stdout_logfile=/var/log/supervisor/fcgiwrap.log +stderr_logfile=/var/log/supervisor/fcgiwrap.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; filtermail-incoming.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:filtermail-incoming] +command=/usr/local/lib/chatmaild/venv/bin/filtermail /usr/local/lib/chatmaild/chatmail.ini incoming +user=vmail +autostart=false +autorestart=true +stdout_logfile=/var/log/supervisor/filtermail-incoming.log +stderr_logfile=/var/log/supervisor/filtermail-incoming.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; filtermail.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:filtermail] +command=/usr/local/lib/chatmaild/venv/bin/filtermail /usr/local/lib/chatmaild/chatmail.ini outgoing +user=vmail +autostart=false +autorestart=true +stdout_logfile=/var/log/supervisor/filtermail.log +stderr_logfile=/var/log/supervisor/filtermail.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; iroh-relay.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:iroh-relay] +command=/usr/local/bin/iroh-relay --config-path /etc/iroh-relay.toml +user=iroh +autostart=false +autorestart=true +stdout_logfile=/var/log/supervisor/iroh-relay.log +stderr_logfile=/var/log/supervisor/iroh-relay.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; lastlogin.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:lastlogin] +command=/usr/local/lib/chatmaild/venv/bin/lastlogin /run/chatmail-lastlogin/lastlogin.socket /usr/local/lib/chatmaild/chatmail.ini +user=vmail +autostart=false +autorestart=true +stdout_logfile=/var/log/supervisor/lastlogin.log +stderr_logfile=/var/log/supervisor/lastlogin.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; nginx.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:nginx] +command=/usr/sbin/nginx -g "daemon off; master_process on;" +autostart=true +autorestart=true +stdout_logfile=/var/log/supervisor/nginx.log +stderr_logfile=/var/log/supervisor/nginx.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; opendkim.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:opendkim] +command=/usr/sbin/opendkim -f +autostart=true +autorestart=true +stdout_logfile=/var/log/supervisor/opendkim.log +stderr_logfile=/var/log/supervisor/opendkim.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; postfix@-.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:postfix] +command=/usr/sbin/postfix start-fg +autostart=true +autorestart=true +stdout_logfile=/var/log/supervisor/postfix.log +stderr_logfile=/var/log/supervisor/postfix.err + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; unbound.service +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[program:unbound] +command=/usr/sbin/unbound -d +autostart=false +autorestart=true +stdout_logfile=/var/log/supervisor/unbound.log +stderr_logfile=/var/log/supervisor/unbound.err From 3d51c08fdd50bbb66590467fa1f3f4ef345812f1 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Fri, 5 Sep 2025 20:10:11 +0300 Subject: [PATCH 24/33] set dafault value for fs_inotify_max_user_instances_and_watchers param - https://github.com/chatmail/relay/pull/614#discussion_r2301902336 --- chatmaild/src/chatmaild/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chatmaild/src/chatmaild/config.py b/chatmaild/src/chatmaild/config.py index 09302d31e..541443963 100644 --- a/chatmaild/src/chatmaild/config.py +++ b/chatmaild/src/chatmaild/config.py @@ -54,7 +54,7 @@ def __init__(self, inipath, params): params.get("change_kernel_settings", "true").lower() == "true" ) self.fs_inotify_max_user_instances_and_watchers = int( - params["fs_inotify_max_user_instances_and_watchers"] + params.get("fs_inotify_max_user_instances_and_watchers", "65535") ) self.imap_rawlog = params.get("imap_rawlog", "false").lower() == "true" if "iroh_relay" not in params: From a3c5c73a2e3478ce85ebd9b412de8ffaabfc211c Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Fri, 5 Sep 2025 20:16:42 +0300 Subject: [PATCH 25/33] delete after a decision has been made during the discussion - https://github.com/chatmail/relay/pull/614#discussion_r2301986955 --- cmdeploy/src/cmdeploy/cmdeploy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdeploy/src/cmdeploy/cmdeploy.py b/cmdeploy/src/cmdeploy/cmdeploy.py index d7bfbdcd8..ef4f477ba 100644 --- a/cmdeploy/src/cmdeploy/cmdeploy.py +++ b/cmdeploy/src/cmdeploy/cmdeploy.py @@ -49,7 +49,7 @@ def init_cmd(args, out): if args.inipath.exists(): if not args.recreate_ini: out.green(f"[WARNING] Path exists, not modifying: {inipath}") - return 1 ### need research - can we set return code as zero? + return 1 else: out.yellow(f"[WARNING] Force argument was provided, deleting config file: {inipath}") inipath.unlink() From 3c146924851c5a0a6bdbed622e893b719db983a3 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Fri, 5 Sep 2025 20:17:21 +0300 Subject: [PATCH 26/33] fix wording - https://github.com/chatmail/relay/pull/614#discussion_r2302052136 --- cmdeploy/src/cmdeploy/cmdeploy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdeploy/src/cmdeploy/cmdeploy.py b/cmdeploy/src/cmdeploy/cmdeploy.py index ef4f477ba..817372efd 100644 --- a/cmdeploy/src/cmdeploy/cmdeploy.py +++ b/cmdeploy/src/cmdeploy/cmdeploy.py @@ -80,7 +80,7 @@ def run_cmd_options(parser): "--skip-dns-check", dest="dns_check_disabled", action="store_true", - help="disable checks nslookup for dns", + help="disable nslookup checks for DNS", ) From 657a00dc0de7c6ea7161ff14a269b6173c1c2e6c Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Fri, 5 Sep 2025 20:30:03 +0300 Subject: [PATCH 27/33] make posix compattable if statements - https://github.com/chatmail/relay/pull/614#discussion_r2302066861 --- docker/files/entrypoint.sh | 2 +- docker/files/setup_chatmail_docker.sh | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/files/entrypoint.sh b/docker/files/entrypoint.sh index 00efc2195..9637f93b7 100755 --- a/docker/files/entrypoint.sh +++ b/docker/files/entrypoint.sh @@ -3,7 +3,7 @@ set -eo pipefail unlink /etc/nginx/sites-enabled/default || true -if [ "${USE_FOREIGN_CERT_MANAGER,,}" == "true" ]; then +if [ "${USE_FOREIGN_CERT_MANAGER,,}" = true ]; then if [ ! -f "$PATH_TO_SSL/fullchain" ]; then echo "Error: file '$PATH_TO_SSL/fullchain' does not exist. Exiting..." > /dev/stderr sleep 2 diff --git a/docker/files/setup_chatmail_docker.sh b/docker/files/setup_chatmail_docker.sh index 7f40c4a35..3f7e4cceb 100755 --- a/docker/files/setup_chatmail_docker.sh +++ b/docker/files/setup_chatmail_docker.sh @@ -48,11 +48,11 @@ monitor_certificates() { ### MAIN -if [ "$DEBUG_COMMANDS_ENABLED" == "true" ]; then +if [ "$DEBUG_COMMANDS_ENABLED" = true ]; then debug_commands fi -if [ "$FORCE_REINIT_INI_FILE" == "true" ]; then +if [ "$FORCE_REINIT_INI_FILE" = true ]; then INI_CMD_ARGS=--force fi @@ -62,7 +62,7 @@ chown opendkim:opendkim /etc/dkimkeys/opendkim.txt # TODO: Move to debug_commands after git clone is moved to dockerfile. git config --global --add safe.directory /opt/chatmail -if [ "$RECREATE_VENV" == "true" ]; then +if [ "$RECREATE_VENV" = true ]; then rm -rf venv fi ./scripts/initenv.sh From 490776e74d98b36f181f513ad95755e5568876a9 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Fri, 5 Sep 2025 20:39:00 +0300 Subject: [PATCH 28/33] Add comment why variables are passed in setup_chatmail.service - https://github.com/chatmail/relay/pull/614#discussion_r2302077414 --- docker/files/entrypoint.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/files/entrypoint.sh b/docker/files/entrypoint.sh index 9637f93b7..d37fae2c3 100755 --- a/docker/files/entrypoint.sh +++ b/docker/files/entrypoint.sh @@ -18,6 +18,9 @@ fi SETUP_CHATMAIL_SERVICE_PATH="${SETUP_CHATMAIL_SERVICE_PATH:-/lib/systemd/system/setup_chatmail.service}" +# In systemd, you must EXPLICITLY specify which environment variables will be used by the process. +# If they are not specified, the systemd unit will be started without variables. +# This is a temporary solution until we completely move away from using systemd in the container. env_vars=$(printenv | cut -d= -f1 | xargs) sed -i "s||$env_vars|g" $SETUP_CHATMAIL_SERVICE_PATH From ed3cba747ad92af706d803fd6a28c73198e8c549 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Fri, 5 Sep 2025 20:52:52 +0300 Subject: [PATCH 29/33] delete CERTS_ROOT_DIR_HOST. Add hardcoded paths to certificates - https://github.com/chatmail/relay/pull/614#discussion_r2300396519 - also delete the supervisord file, it's not ready yet :) --- docker/docker-compose-traefik.yaml | 4 +- docker/example.env | 3 +- docker/files/supervisord.conf | 177 ----------------------------- 3 files changed, 3 insertions(+), 181 deletions(-) delete mode 100644 docker/files/supervisord.conf diff --git a/docker/docker-compose-traefik.yaml b/docker/docker-compose-traefik.yaml index 108ab0d69..ff1e9d5b1 100644 --- a/docker/docker-compose-traefik.yaml +++ b/docker/docker-compose-traefik.yaml @@ -43,7 +43,7 @@ services: ## system - /sys/fs/cgroup:/sys/fs/cgroup:rw # required for systemd - ./:/opt/chatmail - - ${CERTS_ROOT_DIR_HOST}:${CERTS_ROOT_DIR_CONTAINER}:ro + - ./traefik/data/letsencrypt/certs:${CERTS_ROOT_DIR_CONTAINER}:ro ## data - ./data/chatmail:/home @@ -96,7 +96,7 @@ services: max-file: "3" command: - "--configFile=/config.yaml" - - "--certificatesresolvers.letsEncrypt.acme.email=${ACME_EMAIL}" + - "--certificatesresolvers.letsEncrypt.acme.email=${ACME_EMAIL:-}" # ports: # - "80:80" # - "443:443" diff --git a/docker/example.env b/docker/example.env index fdaa193b2..af4a5bf5e 100644 --- a/docker/example.env +++ b/docker/example.env @@ -1,5 +1,4 @@ MAIL_DOMAIN="chat.example.com" -ACME_EMAIL="my.email@gmail.com" +# ACME_EMAIL="my.email@gmail.com" -CERTS_ROOT_DIR_HOST="./traefik/data/letsencrypt/certs" CERTS_ROOT_DIR_CONTAINER="/var/lib/acme/live" diff --git a/docker/files/supervisord.conf b/docker/files/supervisord.conf deleted file mode 100644 index 3e2c5fb81..000000000 --- a/docker/files/supervisord.conf +++ /dev/null @@ -1,177 +0,0 @@ -[supervisord] -nodaemon=true -logfile=/var/log/supervisor/supervisord.log - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; acmetool-redirector.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:acmetool-redirector] -command=/usr/bin/acmetool redirector --service.uid=daemon -autostart=false -autorestart=true -startsecs=5 -stdout_logfile=/var/log/supervisor/acmetool-redirector.log -stderr_logfile=/var/log/supervisor/acmetool-redirector.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; chatmail-metadata.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:chatmail-metadata] -command=/usr/local/lib/chatmaild/venv/bin/chatmail-metadata /run/chatmail-metadata/metadata.socket /usr/local/lib/chatmaild/chatmail.ini -user=vmail -umask=0077 -autostart=false -autorestart=true -stdout_logfile=/var/log/supervisor/chatmail-metadata.log -stderr_logfile=/var/log/supervisor/chatmail-metadata.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; cron.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:cron] -command=/usr/sbin/cron -f $EXTRA_OPTS -environment=EXTRA_OPTS="" -autostart=true -autorestart=true -stdout_logfile=/var/log/supervisor/cron.log -stderr_logfile=/var/log/supervisor/cron.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; dbus.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:dbus] -command=/usr/bin/dbus-daemon --system --nofork --nopidfile -autostart=false -autorestart=true -stdout_logfile=/var/log/supervisor/dbus.log -stderr_logfile=/var/log/supervisor/dbus.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; doveauth.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:doveauth] -command=/usr/local/lib/chatmaild/venv/bin/doveauth /run/doveauth/doveauth.socket /usr/local/lib/chatmaild/chatmail.ini -user=vmail -umask=0077 -autostart=false -autorestart=true -stdout_logfile=/var/log/supervisor/doveauth.log -stderr_logfile=/var/log/supervisor/doveauth.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; dovecot.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:dovecot] -command=/usr/sbin/dovecot -F -autostart=true -autorestart=true -stdout_logfile=/var/log/supervisor/dovecot.log -stderr_logfile=/var/log/supervisor/dovecot.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; echobot.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:echobot] -command=/usr/local/lib/chatmaild/venv/bin/echobot /usr/local/lib/chatmaild/chatmail.ini -user=echobot -directory=/var/lib/echobot -umask=0077 -autostart=false -autorestart=true -stdout_logfile=/var/log/supervisor/echobot.log -stderr_logfile=/var/log/supervisor/echobot.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; fcgiwrap.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:fcgiwrap] -command=/usr/sbin/fcgiwrap -f -user=www-data -autostart=false -autorestart=true -stdout_logfile=/var/log/supervisor/fcgiwrap.log -stderr_logfile=/var/log/supervisor/fcgiwrap.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; filtermail-incoming.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:filtermail-incoming] -command=/usr/local/lib/chatmaild/venv/bin/filtermail /usr/local/lib/chatmaild/chatmail.ini incoming -user=vmail -autostart=false -autorestart=true -stdout_logfile=/var/log/supervisor/filtermail-incoming.log -stderr_logfile=/var/log/supervisor/filtermail-incoming.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; filtermail.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:filtermail] -command=/usr/local/lib/chatmaild/venv/bin/filtermail /usr/local/lib/chatmaild/chatmail.ini outgoing -user=vmail -autostart=false -autorestart=true -stdout_logfile=/var/log/supervisor/filtermail.log -stderr_logfile=/var/log/supervisor/filtermail.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; iroh-relay.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:iroh-relay] -command=/usr/local/bin/iroh-relay --config-path /etc/iroh-relay.toml -user=iroh -autostart=false -autorestart=true -stdout_logfile=/var/log/supervisor/iroh-relay.log -stderr_logfile=/var/log/supervisor/iroh-relay.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; lastlogin.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:lastlogin] -command=/usr/local/lib/chatmaild/venv/bin/lastlogin /run/chatmail-lastlogin/lastlogin.socket /usr/local/lib/chatmaild/chatmail.ini -user=vmail -autostart=false -autorestart=true -stdout_logfile=/var/log/supervisor/lastlogin.log -stderr_logfile=/var/log/supervisor/lastlogin.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; nginx.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:nginx] -command=/usr/sbin/nginx -g "daemon off; master_process on;" -autostart=true -autorestart=true -stdout_logfile=/var/log/supervisor/nginx.log -stderr_logfile=/var/log/supervisor/nginx.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; opendkim.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:opendkim] -command=/usr/sbin/opendkim -f -autostart=true -autorestart=true -stdout_logfile=/var/log/supervisor/opendkim.log -stderr_logfile=/var/log/supervisor/opendkim.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; postfix@-.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:postfix] -command=/usr/sbin/postfix start-fg -autostart=true -autorestart=true -stdout_logfile=/var/log/supervisor/postfix.log -stderr_logfile=/var/log/supervisor/postfix.err - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; unbound.service -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -[program:unbound] -command=/usr/sbin/unbound -d -autostart=false -autorestart=true -stdout_logfile=/var/log/supervisor/unbound.log -stderr_logfile=/var/log/supervisor/unbound.err From 87ac465ab7255c0e4e2640102f73e9f5cfa54787 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Fri, 5 Sep 2025 23:00:15 +0300 Subject: [PATCH 30/33] Add the ability to render the site only in the preferred languages - https://github.com/chatmail/relay/pull/614#issuecomment-3194253557 --- CHANGELOG.md | 1 + chatmaild/src/chatmaild/config.py | 3 + chatmaild/src/chatmaild/ini/chatmail.ini.f | 6 + cmdeploy/src/cmdeploy/www.py | 89 +++- docker/docker-compose-default.yaml | 3 +- docker/docker-compose-traefik.yaml | 1 + www/src/index.md | 52 +-- www/src/info.md | 89 +--- www/src/locales/EN/index.md | 22 + www/src/locales/EN/info.md | 42 ++ www/src/locales/EN/privacy.md | 268 ++++++++++++ www/src/locales/RU/index.md | 22 + www/src/locales/RU/info.md | 38 ++ www/src/locales/RU/privacy.md | 191 +++++++++ www/src/privacy.md | 467 +-------------------- 15 files changed, 668 insertions(+), 626 deletions(-) create mode 100644 www/src/locales/EN/index.md create mode 100644 www/src/locales/EN/info.md create mode 100644 www/src/locales/EN/privacy.md create mode 100644 www/src/locales/RU/index.md create mode 100644 www/src/locales/RU/info.md create mode 100644 www/src/locales/RU/privacy.md diff --git a/CHANGELOG.md b/CHANGELOG.md index a4b251e4f..5a1438a27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ - `acme_email` - Email address used by acmetool to obtain Let's Encrypt certificates (default: empty) - `change_kernel_settings` - Whether to change kernel parameters during installation (default: `True`) - `fs_inotify_max_user_instances_and_watchers` - Value for kernel parameters `fs.inotify.max_user_instances` and `fs.inotify.max_user_watches` (default: `65535`) + - `languages`- List of website languages. (default: `EN`. possible: `EN RU` or `ALL`). Current instances can continue to use the current markdown files, the change will not affect rendering. - Check whether GCC is installed in initenv.sh ([#608](https://github.com/chatmail/relay/pull/608)) diff --git a/chatmaild/src/chatmaild/config.py b/chatmaild/src/chatmaild/config.py index 541443963..6f1dfce33 100644 --- a/chatmaild/src/chatmaild/config.py +++ b/chatmaild/src/chatmaild/config.py @@ -33,6 +33,9 @@ def __init__(self, inipath, params): self.password_min_length = int(params["password_min_length"]) self.passthrough_senders = params["passthrough_senders"].split() self.passthrough_recipients = params["passthrough_recipients"].split() + self.languages = ( + params.get("languages", "EN").split() + ) self.is_development_instance = ( params.get("is_development_instance", "true").lower() == "true" ) diff --git a/chatmaild/src/chatmaild/ini/chatmail.ini.f b/chatmaild/src/chatmaild/ini/chatmail.ini.f index 427956c2d..92e864701 100644 --- a/chatmaild/src/chatmaild/ini/chatmail.ini.f +++ b/chatmaild/src/chatmaild/ini/chatmail.ini.f @@ -49,6 +49,12 @@ # Deployment Details # +# A space-separated list of languages to be displayed on the site. +# Now available languages: EN RU +# You can also use the keyword "ALL" +# NOTE: The order of languages affects their order on the page +languages = EN + # set to "False" to remove the "development instance" banner on the main page. is_development_instance = True diff --git a/cmdeploy/src/cmdeploy/www.py b/cmdeploy/src/cmdeploy/www.py index 2d54d9ea6..95b875d91 100644 --- a/cmdeploy/src/cmdeploy/www.py +++ b/cmdeploy/src/cmdeploy/www.py @@ -10,6 +10,13 @@ from .genqr import gen_qr_png_data +LANGUAGE_NAMES = { + "EN": " 🇬🇧 English", + "RU": " 🇷🇺 Русский", + # "UA": "Українська", + # "FR": "Français", + # "DE": "Deutsch", +} def snapshot_dir_stats(somedir): d = {} @@ -21,13 +28,51 @@ def snapshot_dir_stats(somedir): return d -def prepare_template(source): - assert source.exists(), source - render_vars = {} - render_vars["pagename"] = "home" if source.stem == "index" else source.stem - # tabs usage for multiple languages https://facelessuser.github.io/pymdown-extensions/extensions/blocks/plugins/tab/ - render_vars["markdown_html"] = markdown.markdown(source.read_text(), extensions=['pymdownx.blocks.tab']) - page_layout = source.with_name("page-layout.html").read_text() +def prepare_template(source, locales_dir, languages=["EN"]): + assert source.exists(), f"Template {source} not found." + assert locales_dir.exists(), f"Locales directory {locales_dir} not found." + base_name = source.stem + render_vars = { + "pagename": "home" if base_name == "index" else base_name + } + + selected_langs = ( + sorted([d.name.upper() for d in locales_dir.iterdir() if d.is_dir()]) + if "ALL" in [l.upper() for l in languages] + else [l.upper() for l in languages] + ) + + markdown_blocks = [] + + for lang_code in selected_langs: + lang_folder = locales_dir / lang_code + lang_file = lang_folder / f"{base_name}.md" + lang_name = LANGUAGE_NAMES.get(lang_code, lang_code) + + if lang_file.exists(): + content = lang_file.read_text().strip() + else: + print(f"[WARNING]: Missing file {lang_file}. Inserting fallback message.") + content = "Content for this language is not available, please contact your server administrator." + + markdown_blocks.append(f"/// tab | {lang_name}\n{content}\n///") + + if not markdown_blocks: + print("[WARNING] No valid language content found. Skipping file.") + return None, None + + original_markdown = source.read_text() + combined_markdown = original_markdown.replace("Temporal content", "\n\n".join(markdown_blocks)) + + render_vars["markdown_html"] = markdown.markdown( + combined_markdown, + extensions=["pymdownx.blocks.tab"] + ) + + page_layout_path = source.with_name("page-layout.html") + assert page_layout_path.exists(), f"Missing template: {page_layout_path}" + page_layout = page_layout_path.read_text() + return render_vars, page_layout @@ -64,29 +109,31 @@ def int_to_english(number): def _build_webpages(src_dir, build_dir, config): mail_domain = config.mail_domain + languages = config.languages assert src_dir.exists(), src_dir if not build_dir.exists(): build_dir.mkdir() qr_path = build_dir.joinpath(f"qr-chatmail-invite-{mail_domain}.png") qr_path.write_bytes(gen_qr_png_data(mail_domain).read()) + + locales_dir = src_dir / "locales" for path in src_dir.iterdir(): - if path.suffix == ".md": - render_vars, content = prepare_template(path) - render_vars["username_min_length"] = int_to_english( - config.username_min_length - ) - render_vars["username_max_length"] = int_to_english( - config.username_max_length - ) - render_vars["password_min_length"] = int_to_english( - config.password_min_length - ) + if path.suffix == ".md" and '.' not in path.stem: + render_vars, content = prepare_template(path, locales_dir, languages) + + if render_vars is None: + continue + + render_vars["username_min_length"] = int_to_english(config.username_min_length) + render_vars["username_max_length"] = int_to_english(config.username_max_length) + render_vars["password_min_length"] = int_to_english(config.password_min_length) + target = build_dir.joinpath(path.stem + ".html") # recursive jinja2 rendering - while 1: + while True: new = Template(content).render(config=config, **render_vars) if new == content: break @@ -94,9 +141,11 @@ def _build_webpages(src_dir, build_dir, config): with target.open("w") as f: f.write(content) - elif path.name != "page-layout.html": + + elif path.name != "page-layout.html" and path.name != "locales": target = build_dir.joinpath(path.name) target.write_bytes(path.read_bytes()) + return build_dir diff --git a/docker/docker-compose-default.yaml b/docker/docker-compose-default.yaml index 224ac9380..04978bb43 100644 --- a/docker/docker-compose-default.yaml +++ b/docker/docker-compose-default.yaml @@ -19,7 +19,7 @@ services: options: max-size: "10m" max-file: "3" - environment: + environment: #all possible variables you can check inside README and /chatmaild/src/chatmaild/ini/chatmail.ini.f MAIL_DOMAIN: $MAIL_DOMAIN CHANGE_KERNEL_SETTINGS: "False" ACME_EMAIL: $ACME_EMAIL @@ -31,6 +31,7 @@ services: # ENABLE_CERTS_MONITORING: "true" # CERTS_MONITORING_TIMEOUT: 10 # IS_DEVELOPMENT_INSTANCE: "True" + # LANGUAGES: "EN" # all possible options you can check inside /chatmaild/src/chatmaild/ini/chatmail.ini.f ports: - "80:80" - "443:443" diff --git a/docker/docker-compose-traefik.yaml b/docker/docker-compose-traefik.yaml index ff1e9d5b1..38d7a1af7 100644 --- a/docker/docker-compose-traefik.yaml +++ b/docker/docker-compose-traefik.yaml @@ -33,6 +33,7 @@ services: ENABLE_CERTS_MONITORING: "true" # CERTS_MONITORING_TIMEOUT: 60 # IS_DEVELOPMENT_INSTANCE: "true" + # LANGUAGES: "EN" # all possible options you can check inside /chatmaild/src/chatmaild/ini/chatmail.ini.f ports: - "25:25" - "587:587" diff --git a/www/src/index.md b/www/src/index.md index 8613772b3..42a1e4770 100644 --- a/www/src/index.md +++ b/www/src/index.md @@ -1,56 +1,6 @@ -/// tab | 🇬🇧 English - -## Dear [Delta Chat](https://get.delta.chat) users and newcomers ... - -{% if config.mail_domain != "nine.testrun.org" %} -Welcome to instant, interoperable and [privacy-preserving](privacy.html) messaging :) -{% else %} -Welcome to the default onboarding server ({{ config.mail_domain }}) -for Delta Chat users. For details how it avoids storing personal information -please see our [privacy policy](privacy.html). -{% endif %} - -Get a {{config.mail_domain}} chat profile - -If you are viewing this page on a different device -without a Delta Chat app, -you can also **scan this QR code** with Delta Chat: - - - - -🐣 **Choose** your Avatar and Name - -💬 **Start** chatting with any Delta Chat contacts using [QR invite codes](https://delta.chat/en/help#howtoe2ee) -/// - -/// tab | 🇷🇺 Русский - -## Уважаемые пользователи и новички [Delta Chat](https://get.delta.chat)... - -{% if config.mail_domain != "nine.testrun.org" %} -Добро пожаловать в мир мгновенного, совместимого и [конфиденциального](privacy.html) обмена сообщениями :) -{% else %} -Вы находитесь на сервере по умолчанию ({{ config.mail_domain }}) -для пользователей Delta Chat. Подробную информацию о том, как он избегает хранения личной информации, -см. в нашей [политике конфиденциальности](privacy.html). -{% endif %} - -Создать чат-профиль на {{config.mail_domain}} - -Если вы открыли эту страницу на устройстве, -где нет приложения Delta Chat, вы можете -**отсканировать этот QR-код** с помощью Delta Chat: - - - - -🐣 **Выберите** аватар и имя - -💬 **Начните** чат с любыми контактами Delta Chat через [QR-приглашения](https://delta.chat/ru/help#howtoe2ee) -/// +Temporal content {% if config.is_development_instance == True %}
Note: this is only a temporary development chatmail service
diff --git a/www/src/info.md b/www/src/info.md index fc928db43..96b1084b0 100644 --- a/www/src/info.md +++ b/www/src/info.md @@ -1,90 +1,3 @@ -/// tab | 🇬🇧 English - -## More information - -{{ config.mail_domain }} provides a low-maintenance, resource efficient and -interoperable e-mail service for everyone. What's behind a `chatmail` is -effectively a normal e-mail address just like any other but optimized -for the usage in chats, especially DeltaChat. - - -### Rate and storage limits - -- Un-encrypted messages are blocked to recipients outside - {{config.mail_domain}} but setting up contact via [QR invite codes](https://delta.chat/en/help#howtoe2ee) - allows your messages to pass freely to any outside recipients. - -- You may send up to {{ config.max_user_send_per_minute }} messages per minute. - -- You can store up to [{{ config.max_mailbox_size }} messages on the server](https://delta.chat/en/help#what-happens-if-i-turn-on-delete-old-messages-from-server). - -- Messages are unconditionally removed latest {{ config.delete_mails_after }} days after arriving on the server. - Earlier, if storage may exceed otherwise. - - -### Account deletion - -If you remove a {{ config.mail_domain }} profile from within the Delta Chat app, -then the according account on the server, along with all associated data, -is automatically deleted {{ config.delete_inactive_users_after }} days afterwards. - -If you use multiple devices -then you need to remove the according chat profile from each device -in order for all account data to be removed on the server side. - -If you have any further questions or requests regarding account deletion -please send a message from your account to {{ config.privacy_mail }}. - - -### Who are the operators? Which software is running? - -This chatmail provider is run by a small voluntary group of devs and sysadmins, -who [publically develop chatmail provider setups](https://github.com/deltachat/chatmail). -Chatmail setups aim to be very low-maintenance, resource efficient and -interoperable with any other standards-compliant e-mail service. -/// - -/// tab | 🇷🇺 Русский - -## Дополнительная информация - -{{ config.mail_domain }} предоставляет малозатратный, ресурсосберегающий и совместимый с другими системами почтовый сервис для всех. За `chatmail` фактически скрывается -обычный почтовый адрес, как и любой другой, но оптимизированный -для использования в чатах, особенно DeltaChat. - -### Ограничения по скорости и хранению - -* Незашифрованные сообщения блокируются для получателей вне - {{config.mail_domain}}, но добавление контакта через [QR-коды приглашения](https://delta.chat/en/help#howtoe2ee) - позволяет свободно обмениваться сообщениями между с ним. - -* Вы можете отправлять до {{ config.max_user_send_per_minute }} сообщений в минуту. - -- Вы можете хранить до [{{ config.max_mailbox_size }} сообщений на сервере](https://delta.chat/en/help#what-happens-if-i-turn-on-delete-old-messages-from-server). - -* Сообщения в любом случае будут удалены с сервера через {{ config.delete_mails_after }} дней после поступления на сервер. - Или раньше, если хранилище превышает допустимый объем. - -### Удаление аккаунта - -Если вы удалите профиль {{ config.mail_domain }} через приложение Delta Chat, -соответствующая учетная запись на сервере и все связанные с ней данные -будут автоматически удалены через {{ config.delete_inactive_users_after }} дней. - -Если вы используете несколько устройств, -вам необходимо удалить профиль чата на каждом из них, -чтобы все данные аккаунта были удалены с сервера. - -Если у вас есть дополнительные вопросы или запросы по поводу удаления аккаунта, -пожалуйста, отправьте сообщение со своей учетной записи на {{ config.privacy_mail }}. - -### Кто операторы? Какое ПО используется? - -Этот chatmail провайдер управляется небольшой группой добровольцев — разработчиков и системных администраторов, -которые [публично разрабатывают инфраструктуру chatmail провайдеров](https://github.com/deltachat/chatmail). -Chatmail стремится быть максимально простыми в обслуживании, ресурсосберегающими и -совместимыми с любым другим почтовым сервисом, соответствующим стандартам. - -/// +Temporal content diff --git a/www/src/locales/EN/index.md b/www/src/locales/EN/index.md new file mode 100644 index 000000000..30742cc76 --- /dev/null +++ b/www/src/locales/EN/index.md @@ -0,0 +1,22 @@ +## Dear [Delta Chat](https://get.delta.chat) users and newcomers ... + +{% if config.mail_domain != "nine.testrun.org" %} +Welcome to instant, interoperable and [privacy-preserving](privacy.html) messaging :) +{% else %} +Welcome to the default onboarding server ({{ config.mail_domain }}) +for Delta Chat users. For details how it avoids storing personal information +please see our [privacy policy](privacy.html). +{% endif %} + +Get a {{config.mail_domain}} chat profile + +If you are viewing this page on a different device +without a Delta Chat app, +you can also **scan this QR code** with Delta Chat: + + + + +🐣 **Choose** your Avatar and Name + +💬 **Start** chatting with any Delta Chat contacts using [QR invite codes](https://delta.chat/en/help#howtoe2ee) diff --git a/www/src/locales/EN/info.md b/www/src/locales/EN/info.md new file mode 100644 index 000000000..bce45a9c5 --- /dev/null +++ b/www/src/locales/EN/info.md @@ -0,0 +1,42 @@ +## More information + +{{ config.mail_domain }} provides a low-maintenance, resource efficient and +interoperable e-mail service for everyone. What's behind a `chatmail` is +effectively a normal e-mail address just like any other but optimized +for the usage in chats, especially DeltaChat. + + +### Rate and storage limits + +- Un-encrypted messages are blocked to recipients outside + {{config.mail_domain}} but setting up contact via [QR invite codes](https://delta.chat/en/help#howtoe2ee) + allows your messages to pass freely to any outside recipients. + +- You may send up to {{ config.max_user_send_per_minute }} messages per minute. + +- You can store up to [{{ config.max_mailbox_size }} messages on the server](https://delta.chat/en/help#what-happens-if-i-turn-on-delete-old-messages-from-server). + +- Messages are unconditionally removed latest {{ config.delete_mails_after }} days after arriving on the server. + Earlier, if storage may exceed otherwise. + + +### Account deletion + +If you remove a {{ config.mail_domain }} profile from within the Delta Chat app, +then the according account on the server, along with all associated data, +is automatically deleted {{ config.delete_inactive_users_after }} days afterwards. + +If you use multiple devices +then you need to remove the according chat profile from each device +in order for all account data to be removed on the server side. + +If you have any further questions or requests regarding account deletion +please send a message from your account to {{ config.privacy_mail }}. + + +### Who are the operators? Which software is running? + +This chatmail provider is run by a small voluntary group of devs and sysadmins, +who [publically develop chatmail provider setups](https://github.com/deltachat/chatmail). +Chatmail setups aim to be very low-maintenance, resource efficient and +interoperable with any other standards-compliant e-mail service. diff --git a/www/src/locales/EN/privacy.md b/www/src/locales/EN/privacy.md new file mode 100644 index 000000000..2bc4715b3 --- /dev/null +++ b/www/src/locales/EN/privacy.md @@ -0,0 +1,268 @@ +# Privacy Policy for {{ config.mail_domain }} + +{% if config.mail_domain == "nine.testrun.org" %} +Welcome to `{{config.mail_domain}}`, the default chatmail onboarding server for Delta Chat users. +It is operated on the side by a small sysops team +on a voluntary basis. +See [other chatmail servers](https://delta.chat/en/chatmail) for alternative server operators. +{% endif %} + + +## Summary: No personal data asked or collected + +This chatmail server neither asks for nor retains personal information. +Chatmail servers exist to reliably transmit (store and deliver) end-to-end encrypted messages +between user's devices running the Delta Chat messenger app. +Technically, you may think of a Chatmail server as +an end-to-end encrypted "messaging router" at Internet-scale. + +A chatmail server is very unlike classic e-mail servers (for example Google Mail servers) +that ask for personal data and permanently store messages. +A chatmail server behaves more like the Signal messaging server +but does not know about phone numbers and securely and automatically interoperates +with other chatmail and classic e-mail servers. + +Unlike classic e-mail servers, this chatmail server + +- unconditionally removes messages after {{ config.delete_mails_after }} days, + +- prohibits sending out un-encrypted messages, + +- does not store Internet addresses ("IP addresses"), + +- does not process IP addresses in relation to email addresses. + +Due to the resulting lack of personal data processing +this chatmail server may not require a privacy policy. + +Nevertheless, we provide legal details below to make life easier +for data protection specialists and lawyers scrutinizing chatmail operations. + + + +## 1. Name and contact information + +Responsible for the processing of your personal data is: +``` +{{ config.privacy_postal }} +``` + +E-mail: {{ config.privacy_mail }} + +We have appointed a data protection officer: + +``` +{{ config.privacy_pdo }} +``` + +## 2. Processing when using chat e-mail services + +We provide services optimized for the use from [Delta Chat](https://delta.chat) apps +and process only the data necessary +for the setup and technical execution of message delivery. +The purpose of the processing is that users can +read, write, manage, delete, send, and receive chat messages. +For this purpose, +we operate server-side software +that enables us to send and receive messages. + +We process the following data and details: + +- Outgoing and incoming messages (SMTP) are stored for transit + on behalf of their users until the message can be delivered. + +- E-Mail-Messages are stored for the recipient and made accessible via IMAP protocols, + until explicitly deleted by the user or until a fixed time period is exceeded, + (*usually 4-8 weeks*). + +- IMAP and SMTP protocols are password protected with unique credentials for each account. + +- Users can retrieve or delete all stored messages + without intervention from the operators using standard IMAP client tools. + +- Users can connect to a "realtime relay service" + to establish Peer-to-Peer connection between user devices, + allowing them to send and retrieve ephemeral messages + which are never stored on the chatmail server, also not in encrypted form. + + +### 2.1 Account setup + +Creating an account happens in one of two ways on our mail servers: + +- with a QR invitation token + which is scanned using the Delta Chat app + and then the account is created. + +- by letting Delta Chat otherwise create an account + and register it with a {{ config.mail_domain }} mail server. + +In either case, we process the newly created email address. +No phone numbers, +other email addresses, +or other identifiable data +is currently required. +The legal basis for the processing is +Art. 6 (1) lit. b GDPR, +as you have a usage contract with us +by using our services. + +### 2.2 Processing of E-Mail-Messages + +In addition, +we will process data +to keep the server infrastructure operational +for purposes of e-mail dispatch +and abuse prevention. + +- Therefore, + it is necessary to process the content and/or metadata + (e.g., headers of the email as well as smtp chatter) + of E-Mail-Messages in transit. + +- We will keep logs of messages in transit for a limited time. + These logs are used to debug delivery problems and software bugs. + +In addition, +we process data to protect the systems from excessive use. +Therefore, limits are enforced: + +- rate limits + +- storage limits + +- message size limits + +- any other limit necessary for the whole server to function in a healthy way + and to prevent abuse. + +The processing and use of the above permissions +are performed to provide the service. +The data processing is necessary for the use of our services, +therefore the legal basis of the processing is +Art. 6 (1) lit. b GDPR, +as you have a usage contract with us +by using our services. +The legal basis for the data processing +for the purposes of security and abuse prevention is +Art. 6 (1) lit. f GDPR. +Our legitimate interest results +from the aforementioned purposes. +We will not use the collected data +for the purpose of drawing conclusions +about your person. + + +## 3. Processing when using our Website + +When you visit our website, +the browser used on your end device +automatically sends information to the server of our website. +This information is temporarily stored in a so-called log file. +The following information is collected and stored +until it is automatically deleted +(*usually 7 days*): + +- used type of browser, + +- used operating system, + +- access date and time as well as + +- country of origin and IP address, + +- the requested file name or HTTP resource, + +- the amount of data transferred, + +- the access status (file transferred, file not found, etc.) and + +- the page from which the file was requested. + +This website is hosted by an external service provider (hoster). +The personal data collected on this website is stored +on the hoster's servers. +Our hoster will process your data +only to the extent necessary to fulfill its obligations +to perform under our instructions. +In order to ensure data protection-compliant processing, +we have concluded a data processing agreement with our hoster. + +The aforementioned data is processed by us for the following purposes: + +- Ensuring a reliable connection setup of the website, + +- ensuring a convenient use of our website, + +- checking and ensuring system security and stability, and + +- for other administrative purposes. + +The legal basis for the data processing is +Art. 6 (1) lit. f GDPR. +Our legitimate interest results +from the aforementioned purposes of data collection. +We will not use the collected data +for the purpose of drawing conclusions about your person. + +## 4. Transfer of Data + +We do not retain any personal data but e-mail messages waiting to be delivered +may contain personal data. +Any such residual personal data will not be transferred to third parties +for purposes other than those listed below: + +a) you have given your express consent +in accordance with Art. 6 para. 1 sentence 1 lit. a GDPR, + +b) the disclosure is necessary for the assertion, exercise or defence of legal claims +pursuant to Art. 6 (1) sentence 1 lit. f GDPR +and there is no reason to assume that you have +an overriding interest worthy of protection +in the non-disclosure of your data, + +c) in the event that there is a legal obligation to disclose your data +pursuant to Art. 6 para. 1 sentence 1 lit. c GDPR, +as well as + +d) this is legally permissible and necessary +in accordance with Art. 6 Para. 1 S. 1 lit. b GDPR +for the processing of contractual relationships with you, + +e) this is carried out by a service provider +acting on our behalf and on our exclusive instructions, +whom we have carefully selected (Art. 28 (1) GDPR) +and with whom we have concluded a corresponding contract on commissioned processing (Art. 28 (3) GDPR), +which obliges our contractor, +among other things, +to implement appropriate security measures +and grants us comprehensive control powers. + +## 5. Rights of the data subject + +The rights arise from Articles 12 to 23 GDPR. +Since no personal data is stored on our servers, +even in encrypted form, +there is no need to provide information +on these or possible objections. +A deletion can be made +directly in the Delta Chat email messenger. + +If you have any questions or complaints, +please feel free to contact us by email: +{{ config.privacy_mail }} + +As a rule, you can contact the supervisory authority of your usual place of residence +or workplace +or our registered office for this purpose. +The supervisory authority responsible for our place of business +is the `{{ config.privacy_supervisor }}`. + + +## 6. Validity of this privacy policy + +This data protection declaration is valid +as of *October 2024*. +Due to the further development of our service and offers +or due to changed legal or official requirements, +it may become necessary to revise this data protection declaration from time to time. diff --git a/www/src/locales/RU/index.md b/www/src/locales/RU/index.md new file mode 100644 index 000000000..d1e3d3dc4 --- /dev/null +++ b/www/src/locales/RU/index.md @@ -0,0 +1,22 @@ +## Уважаемые пользователи и новички [Delta Chat](https://get.delta.chat)... + +{% if config.mail_domain != "nine.testrun.org" %} +Добро пожаловать в мир мгновенного, совместимого и [конфиденциального](privacy.html) обмена сообщениями :) +{% else %} +Вы находитесь на сервере по умолчанию ({{ config.mail_domain }}) +для пользователей Delta Chat. Подробную информацию о том, как он избегает хранения личной информации, +см. в нашей [политике конфиденциальности](privacy.html). +{% endif %} + +Создать чат-профиль на {{config.mail_domain}} + +Если вы открыли эту страницу на устройстве, +где нет приложения Delta Chat, вы можете +**отсканировать этот QR-код** с помощью Delta Chat: + + + + +🐣 **Выберите** аватар и имя + +💬 **Начните** чат с любыми контактами Delta Chat через [QR-приглашения](https://delta.chat/ru/help#howtoe2ee) diff --git a/www/src/locales/RU/info.md b/www/src/locales/RU/info.md new file mode 100644 index 000000000..86abf8580 --- /dev/null +++ b/www/src/locales/RU/info.md @@ -0,0 +1,38 @@ +## Дополнительная информация + +{{ config.mail_domain }} предоставляет малозатратный, ресурсосберегающий и совместимый с другими системами почтовый сервис для всех. За `chatmail` фактически скрывается +обычный почтовый адрес, как и любой другой, но оптимизированный +для использования в чатах, особенно DeltaChat. + +### Ограничения по скорости и хранению + +* Незашифрованные сообщения блокируются для получателей вне + {{config.mail_domain}}, но добавление контакта через [QR-коды приглашения](https://delta.chat/en/help#howtoe2ee) + позволяет свободно обмениваться сообщениями между с ним. + +* Вы можете отправлять до {{ config.max_user_send_per_minute }} сообщений в минуту. + +- Вы можете хранить до [{{ config.max_mailbox_size }} сообщений на сервере](https://delta.chat/en/help#what-happens-if-i-turn-on-delete-old-messages-from-server). + +* Сообщения в любом случае будут удалены с сервера через {{ config.delete_mails_after }} дней после поступления на сервер. + Или раньше, если хранилище превышает допустимый объем. + +### Удаление аккаунта + +Если вы удалите профиль {{ config.mail_domain }} через приложение Delta Chat, +соответствующая учетная запись на сервере и все связанные с ней данные +будут автоматически удалены через {{ config.delete_inactive_users_after }} дней. + +Если вы используете несколько устройств, +вам необходимо удалить профиль чата на каждом из них, +чтобы все данные аккаунта были удалены с сервера. + +Если у вас есть дополнительные вопросы или запросы по поводу удаления аккаунта, +пожалуйста, отправьте сообщение со своей учетной записи на {{ config.privacy_mail }}. + +### Кто операторы? Какое ПО используется? + +Этот chatmail провайдер управляется небольшой группой добровольцев — разработчиков и системных администраторов, +которые [публично разрабатывают инфраструктуру chatmail провайдеров](https://github.com/deltachat/chatmail). +Chatmail стремится быть максимально простыми в обслуживании, ресурсосберегающими и +совместимыми с любым другим почтовым сервисом, соответствующим стандартам. diff --git a/www/src/locales/RU/privacy.md b/www/src/locales/RU/privacy.md new file mode 100644 index 000000000..c85555b02 --- /dev/null +++ b/www/src/locales/RU/privacy.md @@ -0,0 +1,191 @@ +# Политика конфиденциальности для {{ config.mail_domain }} + +{% if config.mail_domain == "nine.testrun.org" %} +Добро пожаловать на `{{config.mail_domain}}` — это основной сервер Chatmail для новых пользователей Delta Chat. +Он поддерживается небольшой командой системных администраторов на добровольной основе. +Альтернативные сервера вы можете найти [здесь](https://delta.chat/en/chatmail). +{% endif %} + +## Кратко: Личные данные не запрашиваются и не собираются + +Этот сервер Chatmail не запрашивает и не сохраняет личную информацию. +Серверы Chatmail существуют исключительно для надёжной передачи (временного хранения и доставки) зашифрованных сообщений между устройствами пользователей, использующих мессенджер Delta Chat. + +Технически, Chatmail-сервер можно представить как «маршрутизатор сообщений» с поддержкой сквозного шифрования в масштабе интернета. + +В отличие от классических почтовых сервисов (например, Gmail), +Chatmail-серверы не запрашивают личные данные и не хранят письма постоянно. +Они ближе по устройству к серверам Signal, +однако не используют номера телефонов и могут безопасно и автоматически взаимодействовать как с другими Chatmail-серверами, так и с обычной электронной почтой. + +Отличия от традиционных почтовых серверов: + +- безусловное удаление сообщений через {{ config.delete_mails_after }} дней; +- невозможность отправки незашифрованных сообщений; +- отсутствие хранения IP-адресов; +- IP-адреса не обрабатываются в связке с адресами электронной почты. + +Из-за отсутствия обработки персональных данных +данный сервер, возможно, формально не обязан иметь политику конфиденциальности. + +Тем не менее, ниже приведена юридическая информация +для удобства специалистов по защите данных и юристов, изучающих работу Chatmail. + +--- + +## 1. Название и контактная информация + +Ответственный за обработку ваших персональных данных: + +``` +{{ config.privacy_postal }} +``` + +Эл. почта: {{ config.privacy_mail }} + +Назначен ответственный по защите данных: + +``` +{{ config.privacy_pdo }} +``` + +--- + +## 2. Обработка при использовании чата и электронной почты + +Мы предоставляем сервисы, оптимизированные для работы с приложением [Delta Chat](https://delta.chat), +и обрабатываем только те данные, которые необходимы для настройки и технической реализации доставки сообщений. +Цель обработки — дать пользователям возможность читать, писать, управлять, удалять, отправлять и получать сообщения. + +Для этого мы используем серверное ПО, обеспечивающее передачу сообщений. + +Обрабатываются следующие данные: + +- Исходящие и входящие сообщения (SMTP) временно хранятся до их доставки получателю; +- Сообщения доступны получателю через IMAP до их удаления пользователем или по истечении установленного срока + (*обычно 4–8 недель*); +- Протоколы IMAP и SMTP защищены паролем, уникальным для каждого аккаунта; +- Пользователи могут самостоятельно просматривать или удалять сообщения через любой стандартный IMAP-клиент; +- Также возможно подключение к «службе передачи в реальном времени», + которая устанавливает P2P-соединение между устройствами и позволяет отправлять временные сообщения, + которые *никогда* не сохраняются на сервере — даже в зашифрованном виде. + +### 2.1 Создание аккаунта + +Аккаунт создаётся одним из двух способов: + +- с помощью QR-кода приглашения, + отсканированного через приложение Delta Chat; + +- автоматически, при создании и регистрации аккаунта в {{ config.mail_domain }} через приложение Delta Chat. + +В любом случае, обрабатывается только созданный адрес электронной почты. +Номера телефонов, другие адреса электронной почты или любые другие идентификаторы не требуются. +Правовое основание для обработки — +статья 6 (1) пункт b Общего регламента по защите данных (GDPR), +так как вы заключаете пользовательский договор, пользуясь нашим сервисом. + +### 2.2 Обработка почтовых сообщений + +Кроме того, мы обрабатываем данные, +необходимые для обеспечения стабильной работы инфраструктуры сервера, +доставки сообщений и предотвращения злоупотреблений. + +- Поэтому может потребоваться обработка содержимого и/или метаданных + (например, заголовков писем и технической информации SMTP) во время передачи; + +- Мы храним логи передаваемых сообщений ограниченное время — + они используются для устранения проблем с доставкой и ошибок ПО. + +Также мы вводим ограничения для защиты системы от перегрузок: + +- ограничения скорости (rate limits), +- лимиты на объём хранения, +- ограничения на размер сообщений, +- любые другие меры, необходимые для стабильной работы сервера и предотвращения злоупотреблений. + +Обработка вышеуказанных данных необходима для предоставления сервиса. +Правовое основание — статья 6 (1) пункт b GDPR. +Обработка данных в целях безопасности и предотвращения злоупотреблений основана на статье 6 (1) пункт f GDPR, +и соответствует нашим законным интересам. + +Мы не используем собранные данные для определения вашей личности. + +--- + +## 3. Обработка при посещении сайта + +При посещении нашего сайта браузер вашего устройства +автоматически передаёт определённую информацию на сервер, +где она временно сохраняется в так называемых лог-файлах. +Эти данные автоматически удаляются (обычно через *7 дней*). + +Среди собираемых данных: + +- тип используемого браузера, +- операционная система, +- дата и время доступа, +- страна и IP-адрес, +- запрашиваемый файл или ресурс, +- объём переданных данных, +- статус доступа (успешно, ошибка и т.п.), +- страница, с которой был сделан запрос. + +Хостинг нашего сайта осуществляется внешним провайдером. +Личные данные, собираемые на сайте, хранятся на его серверах. +Провайдер обрабатывает данные строго по нашим инструкциям, +в пределах заключённого договора на обработку данных (ст. 28 GDPR). + +Цели обработки: + +- обеспечение стабильного подключения к сайту; +- удобство использования сайта; +- контроль безопасности и стабильности системы; +- административные цели. + +Правовое основание — статья 6 (1) пункт f GDPR. +Собранные данные не используются для установления вашей личности. + +--- + +## 4. Передача данных + +Мы не сохраняем личные данные, +но письма, ожидающие доставки, могут содержать личную информацию. +Такие данные не передаются третьим лицам, за исключением следующих случаев: + +a) при наличии вашего явного согласия (ст. 6 п.1 п. a GDPR); + +b) если передача необходима для защиты прав, интересов или правовой позиции (ст. 6 п.1 п. f GDPR); + +c) если это требуется по закону (ст. 6 п.1 п. c GDPR); + +d) если это необходимо для исполнения договора с вами (ст. 6 п.1 п. b GDPR); + +e) если обработка осуществляется сервис-провайдером по нашему поручению, + с которым заключён договор (ст. 28 GDPR), + предусматривающий меры безопасности и контроль с нашей стороны. + +--- + +## 5. Права субъектов данных + +Ваши права закреплены в статьях 12–23 GDPR. +Так как сервер не хранит персональные данные — даже в зашифрованном виде — +предоставление информации или подача возражений не требуются. +Удаление данных можно выполнить напрямую через приложение Delta Chat. + +Если у вас есть вопросы или жалобы, напишите нам: +{{ config.privacy_mail }} + +Также вы можете обратиться в надзорный орган по месту вашего проживания, +работы или к органу, ответственному за нашу деятельность: +`{{ config.privacy_supervisor }}`. + +--- + +## 6. Актуальность политики конфиденциальности + +Настоящая политика действует с *октября 2024 года*. +В случае изменений в услугах или законодательства +она может быть обновлена. diff --git a/www/src/privacy.md b/www/src/privacy.md index cde7d0303..6aaa0f8b2 100644 --- a/www/src/privacy.md +++ b/www/src/privacy.md @@ -1,468 +1,3 @@ -/// tab | 🇬🇧 English - -# Privacy Policy for {{ config.mail_domain }} - -{% if config.mail_domain == "nine.testrun.org" %} -Welcome to `{{config.mail_domain}}`, the default chatmail onboarding server for Delta Chat users. -It is operated on the side by a small sysops team -on a voluntary basis. -See [other chatmail servers](https://delta.chat/en/chatmail) for alternative server operators. -{% endif %} - - -## Summary: No personal data asked or collected - -This chatmail server neither asks for nor retains personal information. -Chatmail servers exist to reliably transmit (store and deliver) end-to-end encrypted messages -between user's devices running the Delta Chat messenger app. -Technically, you may think of a Chatmail server as -an end-to-end encrypted "messaging router" at Internet-scale. - -A chatmail server is very unlike classic e-mail servers (for example Google Mail servers) -that ask for personal data and permanently store messages. -A chatmail server behaves more like the Signal messaging server -but does not know about phone numbers and securely and automatically interoperates -with other chatmail and classic e-mail servers. - -Unlike classic e-mail servers, this chatmail server - -- unconditionally removes messages after {{ config.delete_mails_after }} days, - -- prohibits sending out un-encrypted messages, - -- does not store Internet addresses ("IP addresses"), - -- does not process IP addresses in relation to email addresses. - -Due to the resulting lack of personal data processing -this chatmail server may not require a privacy policy. - -Nevertheless, we provide legal details below to make life easier -for data protection specialists and lawyers scrutinizing chatmail operations. - - - -## 1. Name and contact information - -Responsible for the processing of your personal data is: -``` -{{ config.privacy_postal }} -``` - -E-mail: {{ config.privacy_mail }} - -We have appointed a data protection officer: - -``` -{{ config.privacy_pdo }} -``` - -## 2. Processing when using chat e-mail services - -We provide services optimized for the use from [Delta Chat](https://delta.chat) apps -and process only the data necessary -for the setup and technical execution of message delivery. -The purpose of the processing is that users can -read, write, manage, delete, send, and receive chat messages. -For this purpose, -we operate server-side software -that enables us to send and receive messages. - -We process the following data and details: - -- Outgoing and incoming messages (SMTP) are stored for transit - on behalf of their users until the message can be delivered. - -- E-Mail-Messages are stored for the recipient and made accessible via IMAP protocols, - until explicitly deleted by the user or until a fixed time period is exceeded, - (*usually 4-8 weeks*). - -- IMAP and SMTP protocols are password protected with unique credentials for each account. - -- Users can retrieve or delete all stored messages - without intervention from the operators using standard IMAP client tools. - -- Users can connect to a "realtime relay service" - to establish Peer-to-Peer connection between user devices, - allowing them to send and retrieve ephemeral messages - which are never stored on the chatmail server, also not in encrypted form. - - -### 2.1 Account setup - -Creating an account happens in one of two ways on our mail servers: - -- with a QR invitation token - which is scanned using the Delta Chat app - and then the account is created. - -- by letting Delta Chat otherwise create an account - and register it with a {{ config.mail_domain }} mail server. - -In either case, we process the newly created email address. -No phone numbers, -other email addresses, -or other identifiable data -is currently required. -The legal basis for the processing is -Art. 6 (1) lit. b GDPR, -as you have a usage contract with us -by using our services. - -### 2.2 Processing of E-Mail-Messages - -In addition, -we will process data -to keep the server infrastructure operational -for purposes of e-mail dispatch -and abuse prevention. - -- Therefore, - it is necessary to process the content and/or metadata - (e.g., headers of the email as well as smtp chatter) - of E-Mail-Messages in transit. - -- We will keep logs of messages in transit for a limited time. - These logs are used to debug delivery problems and software bugs. - -In addition, -we process data to protect the systems from excessive use. -Therefore, limits are enforced: - -- rate limits - -- storage limits - -- message size limits - -- any other limit necessary for the whole server to function in a healthy way - and to prevent abuse. - -The processing and use of the above permissions -are performed to provide the service. -The data processing is necessary for the use of our services, -therefore the legal basis of the processing is -Art. 6 (1) lit. b GDPR, -as you have a usage contract with us -by using our services. -The legal basis for the data processing -for the purposes of security and abuse prevention is -Art. 6 (1) lit. f GDPR. -Our legitimate interest results -from the aforementioned purposes. -We will not use the collected data -for the purpose of drawing conclusions -about your person. - - -## 3. Processing when using our Website - -When you visit our website, -the browser used on your end device -automatically sends information to the server of our website. -This information is temporarily stored in a so-called log file. -The following information is collected and stored -until it is automatically deleted -(*usually 7 days*): - -- used type of browser, - -- used operating system, - -- access date and time as well as - -- country of origin and IP address, - -- the requested file name or HTTP resource, - -- the amount of data transferred, - -- the access status (file transferred, file not found, etc.) and - -- the page from which the file was requested. - -This website is hosted by an external service provider (hoster). -The personal data collected on this website is stored -on the hoster's servers. -Our hoster will process your data -only to the extent necessary to fulfill its obligations -to perform under our instructions. -In order to ensure data protection-compliant processing, -we have concluded a data processing agreement with our hoster. - -The aforementioned data is processed by us for the following purposes: - -- Ensuring a reliable connection setup of the website, - -- ensuring a convenient use of our website, - -- checking and ensuring system security and stability, and - -- for other administrative purposes. - -The legal basis for the data processing is -Art. 6 (1) lit. f GDPR. -Our legitimate interest results -from the aforementioned purposes of data collection. -We will not use the collected data -for the purpose of drawing conclusions about your person. - -## 4. Transfer of Data - -We do not retain any personal data but e-mail messages waiting to be delivered -may contain personal data. -Any such residual personal data will not be transferred to third parties -for purposes other than those listed below: - -a) you have given your express consent -in accordance with Art. 6 para. 1 sentence 1 lit. a GDPR, - -b) the disclosure is necessary for the assertion, exercise or defence of legal claims -pursuant to Art. 6 (1) sentence 1 lit. f GDPR -and there is no reason to assume that you have -an overriding interest worthy of protection -in the non-disclosure of your data, - -c) in the event that there is a legal obligation to disclose your data -pursuant to Art. 6 para. 1 sentence 1 lit. c GDPR, -as well as - -d) this is legally permissible and necessary -in accordance with Art. 6 Para. 1 S. 1 lit. b GDPR -for the processing of contractual relationships with you, - -e) this is carried out by a service provider -acting on our behalf and on our exclusive instructions, -whom we have carefully selected (Art. 28 (1) GDPR) -and with whom we have concluded a corresponding contract on commissioned processing (Art. 28 (3) GDPR), -which obliges our contractor, -among other things, -to implement appropriate security measures -and grants us comprehensive control powers. - -## 5. Rights of the data subject - -The rights arise from Articles 12 to 23 GDPR. -Since no personal data is stored on our servers, -even in encrypted form, -there is no need to provide information -on these or possible objections. -A deletion can be made -directly in the Delta Chat email messenger. - -If you have any questions or complaints, -please feel free to contact us by email: -{{ config.privacy_mail }} - -As a rule, you can contact the supervisory authority of your usual place of residence -or workplace -or our registered office for this purpose. -The supervisory authority responsible for our place of business -is the `{{ config.privacy_supervisor }}`. - - -## 6. Validity of this privacy policy - -This data protection declaration is valid -as of *October 2024*. -Due to the further development of our service and offers -or due to changed legal or official requirements, -it may become necessary to revise this data protection declaration from time to time. -/// - -/// tab | 🇷🇺 Русский - -# Политика конфиденциальности для {{ config.mail_domain }} - -{% if config.mail_domain == "nine.testrun.org" %} -Добро пожаловать на `{{config.mail_domain}}` — это основной сервер Chatmail для новых пользователей Delta Chat. -Он поддерживается небольшой командой системных администраторов на добровольной основе. -Альтернативные сервера вы можете найти [здесь](https://delta.chat/en/chatmail). -{% endif %} - -## Кратко: Личные данные не запрашиваются и не собираются - -Этот сервер Chatmail не запрашивает и не сохраняет личную информацию. -Серверы Chatmail существуют исключительно для надёжной передачи (временного хранения и доставки) зашифрованных сообщений между устройствами пользователей, использующих мессенджер Delta Chat. - -Технически, Chatmail-сервер можно представить как «маршрутизатор сообщений» с поддержкой сквозного шифрования в масштабе интернета. - -В отличие от классических почтовых сервисов (например, Gmail), -Chatmail-серверы не запрашивают личные данные и не хранят письма постоянно. -Они ближе по устройству к серверам Signal, -однако не используют номера телефонов и могут безопасно и автоматически взаимодействовать как с другими Chatmail-серверами, так и с обычной электронной почтой. - -Отличия от традиционных почтовых серверов: - -- безусловное удаление сообщений через {{ config.delete_mails_after }} дней; -- невозможность отправки незашифрованных сообщений; -- отсутствие хранения IP-адресов; -- IP-адреса не обрабатываются в связке с адресами электронной почты. - -Из-за отсутствия обработки персональных данных -данный сервер, возможно, формально не обязан иметь политику конфиденциальности. - -Тем не менее, ниже приведена юридическая информация -для удобства специалистов по защите данных и юристов, изучающих работу Chatmail. - ---- - -## 1. Название и контактная информация - -Ответственный за обработку ваших персональных данных: - -``` -{{ config.privacy_postal }} -``` - -Эл. почта: {{ config.privacy_mail }} - -Назначен ответственный по защите данных: - -``` -{{ config.privacy_pdo }} -``` - ---- - -## 2. Обработка при использовании чата и электронной почты - -Мы предоставляем сервисы, оптимизированные для работы с приложением [Delta Chat](https://delta.chat), -и обрабатываем только те данные, которые необходимы для настройки и технической реализации доставки сообщений. -Цель обработки — дать пользователям возможность читать, писать, управлять, удалять, отправлять и получать сообщения. - -Для этого мы используем серверное ПО, обеспечивающее передачу сообщений. - -Обрабатываются следующие данные: - -- Исходящие и входящие сообщения (SMTP) временно хранятся до их доставки получателю; -- Сообщения доступны получателю через IMAP до их удаления пользователем или по истечении установленного срока - (*обычно 4–8 недель*); -- Протоколы IMAP и SMTP защищены паролем, уникальным для каждого аккаунта; -- Пользователи могут самостоятельно просматривать или удалять сообщения через любой стандартный IMAP-клиент; -- Также возможно подключение к «службе передачи в реальном времени», - которая устанавливает P2P-соединение между устройствами и позволяет отправлять временные сообщения, - которые *никогда* не сохраняются на сервере — даже в зашифрованном виде. - -### 2.1 Создание аккаунта - -Аккаунт создаётся одним из двух способов: - -- с помощью QR-кода приглашения, - отсканированного через приложение Delta Chat; - -- автоматически, при создании и регистрации аккаунта в {{ config.mail_domain }} через приложение Delta Chat. - -В любом случае, обрабатывается только созданный адрес электронной почты. -Номера телефонов, другие адреса электронной почты или любые другие идентификаторы не требуются. -Правовое основание для обработки — -статья 6 (1) пункт b Общего регламента по защите данных (GDPR), -так как вы заключаете пользовательский договор, пользуясь нашим сервисом. - -### 2.2 Обработка почтовых сообщений - -Кроме того, мы обрабатываем данные, -необходимые для обеспечения стабильной работы инфраструктуры сервера, -доставки сообщений и предотвращения злоупотреблений. - -- Поэтому может потребоваться обработка содержимого и/или метаданных - (например, заголовков писем и технической информации SMTP) во время передачи; - -- Мы храним логи передаваемых сообщений ограниченное время — - они используются для устранения проблем с доставкой и ошибок ПО. - -Также мы вводим ограничения для защиты системы от перегрузок: - -- ограничения скорости (rate limits), -- лимиты на объём хранения, -- ограничения на размер сообщений, -- любые другие меры, необходимые для стабильной работы сервера и предотвращения злоупотреблений. - -Обработка вышеуказанных данных необходима для предоставления сервиса. -Правовое основание — статья 6 (1) пункт b GDPR. -Обработка данных в целях безопасности и предотвращения злоупотреблений основана на статье 6 (1) пункт f GDPR, -и соответствует нашим законным интересам. - -Мы не используем собранные данные для определения вашей личности. - ---- - -## 3. Обработка при посещении сайта - -При посещении нашего сайта браузер вашего устройства -автоматически передаёт определённую информацию на сервер, -где она временно сохраняется в так называемых лог-файлах. -Эти данные автоматически удаляются (обычно через *7 дней*). - -Среди собираемых данных: - -- тип используемого браузера, -- операционная система, -- дата и время доступа, -- страна и IP-адрес, -- запрашиваемый файл или ресурс, -- объём переданных данных, -- статус доступа (успешно, ошибка и т.п.), -- страница, с которой был сделан запрос. - -Хостинг нашего сайта осуществляется внешним провайдером. -Личные данные, собираемые на сайте, хранятся на его серверах. -Провайдер обрабатывает данные строго по нашим инструкциям, -в пределах заключённого договора на обработку данных (ст. 28 GDPR). - -Цели обработки: - -- обеспечение стабильного подключения к сайту; -- удобство использования сайта; -- контроль безопасности и стабильности системы; -- административные цели. - -Правовое основание — статья 6 (1) пункт f GDPR. -Собранные данные не используются для установления вашей личности. - ---- - -## 4. Передача данных - -Мы не сохраняем личные данные, -но письма, ожидающие доставки, могут содержать личную информацию. -Такие данные не передаются третьим лицам, за исключением следующих случаев: - -a) при наличии вашего явного согласия (ст. 6 п.1 п. a GDPR); - -b) если передача необходима для защиты прав, интересов или правовой позиции (ст. 6 п.1 п. f GDPR); - -c) если это требуется по закону (ст. 6 п.1 п. c GDPR); - -d) если это необходимо для исполнения договора с вами (ст. 6 п.1 п. b GDPR); - -e) если обработка осуществляется сервис-провайдером по нашему поручению, - с которым заключён договор (ст. 28 GDPR), - предусматривающий меры безопасности и контроль с нашей стороны. - ---- - -## 5. Права субъектов данных - -Ваши права закреплены в статьях 12–23 GDPR. -Так как сервер не хранит персональные данные — даже в зашифрованном виде — -предоставление информации или подача возражений не требуются. -Удаление данных можно выполнить напрямую через приложение Delta Chat. - -Если у вас есть вопросы или жалобы, напишите нам: -{{ config.privacy_mail }} - -Также вы можете обратиться в надзорный орган по месту вашего проживания, -работы или к органу, ответственному за нашу деятельность: -`{{ config.privacy_supervisor }}`. - ---- - -## 6. Актуальность политики конфиденциальности - -Настоящая политика действует с *октября 2024 года*. -В случае изменений в услугах или законодательства -она может быть обновлена. -/// +Temporal content From b86c9778765287d199197777962e03eda56fb6df Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 6 Sep 2025 17:12:03 +0300 Subject: [PATCH 31/33] - pedantic fix - https://github.com/chatmail/relay/pull/614#discussion_r2327037908 --- docker/docker-compose-default.yaml | 2 +- docker/docker-compose-traefik.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/docker-compose-default.yaml b/docker/docker-compose-default.yaml index 04978bb43..730e63a12 100644 --- a/docker/docker-compose-default.yaml +++ b/docker/docker-compose-default.yaml @@ -19,7 +19,7 @@ services: options: max-size: "10m" max-file: "3" - environment: #all possible variables you can check inside README and /chatmaild/src/chatmaild/ini/chatmail.ini.f + environment: # all possible variables you can check inside README and /chatmaild/src/chatmaild/ini/chatmail.ini.f MAIL_DOMAIN: $MAIL_DOMAIN CHANGE_KERNEL_SETTINGS: "False" ACME_EMAIL: $ACME_EMAIL diff --git a/docker/docker-compose-traefik.yaml b/docker/docker-compose-traefik.yaml index 38d7a1af7..52b9fdeb7 100644 --- a/docker/docker-compose-traefik.yaml +++ b/docker/docker-compose-traefik.yaml @@ -21,7 +21,7 @@ services: options: max-size: "10m" max-file: "3" - environment: #all possible variables you can check inside README and /chatmaild/src/chatmaild/ini/chatmail.ini.f + environment: # all possible variables you can check inside README and /chatmaild/src/chatmaild/ini/chatmail.ini.f MAIL_DOMAIN: $MAIL_DOMAIN # MAX_MESSAGE_SIZE: "50M" # DEBUG_COMMANDS_ENABLED: "true" From 74faefa0bc832d585a453926069f235c4bd50bb6 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 6 Sep 2025 17:13:15 +0300 Subject: [PATCH 32/33] delete ancestral legacy - https://github.com/chatmail/relay/pull/614#discussion_r2327034307 --- cmdeploy/src/cmdeploy/www.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdeploy/src/cmdeploy/www.py b/cmdeploy/src/cmdeploy/www.py index 95b875d91..012df7635 100644 --- a/cmdeploy/src/cmdeploy/www.py +++ b/cmdeploy/src/cmdeploy/www.py @@ -120,7 +120,7 @@ def _build_webpages(src_dir, build_dir, config): locales_dir = src_dir / "locales" for path in src_dir.iterdir(): - if path.suffix == ".md" and '.' not in path.stem: + if path.suffix == ".md": render_vars, content = prepare_template(path, locales_dir, languages) if render_vars is None: From dfe2c003b312d504aa14f97ae6a0be42753b4f35 Mon Sep 17 00:00:00 2001 From: Keonik1 Date: Sat, 6 Sep 2025 17:29:26 +0300 Subject: [PATCH 33/33] delete tabs if only one language selected - https://github.com/chatmail/relay/pull/614#discussion_r2327027639 --- cmdeploy/src/cmdeploy/www.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cmdeploy/src/cmdeploy/www.py b/cmdeploy/src/cmdeploy/www.py index 012df7635..8ce12406d 100644 --- a/cmdeploy/src/cmdeploy/www.py +++ b/cmdeploy/src/cmdeploy/www.py @@ -44,6 +44,10 @@ def prepare_template(source, locales_dir, languages=["EN"]): markdown_blocks = [] + tabs_enabled = False + if len(selected_langs) > 1: + tabs_enabled = True + for lang_code in selected_langs: lang_folder = locales_dir / lang_code lang_file = lang_folder / f"{base_name}.md" @@ -55,7 +59,11 @@ def prepare_template(source, locales_dir, languages=["EN"]): print(f"[WARNING]: Missing file {lang_file}. Inserting fallback message.") content = "Content for this language is not available, please contact your server administrator." - markdown_blocks.append(f"/// tab | {lang_name}\n{content}\n///") + if tabs_enabled: + markdown_blocks.append(f"/// tab | {lang_name}\n{content}\n///") + continue + + markdown_blocks.append(content) if not markdown_blocks: print("[WARNING] No valid language content found. Skipping file.")