Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,9 @@ cython_debug/
#.idea/

chatmail.zone

# docker
/data/
/custom/
docker-compose.yaml
.env
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@
- Add `--skip-dns-check` argument to `cmdeploy run` command, which disables DNS record checking before installation.
([#661](https://github.com/chatmail/relay/pull/661))

- 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 configuration parameters
([#614](https://github.com/chatmail/relay/pull/614)):
- `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`)

## 1.7.0 2025-09-11

- Make www upload path configurable
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -99,6 +100,12 @@ 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

We have experimental support for [docker compose](./docs/DOCKER_INSTALLATION_EN.md),
but it is not covered by automated tests yet,
so don't expect everything to work.

### Other helpful commands

To check the status of your remotely running chatmail service:
Expand Down
6 changes: 6 additions & 0 deletions chatmaild/src/chatmaild/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ def __init__(self, inipath, params):
self.mtail_address = params.get("mtail_address")
self.disable_ipv6 = params.get("disable_ipv6", "false").lower() == "true"
self.acme_email = params.get("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"]
Expand Down
10 changes: 10 additions & 0 deletions chatmaild/src/chatmaild/ini/chatmail.ini.f
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@
# Your email adress, which will be used in acmetool to manage Let's Encrypt SSL certificates
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
Expand Down
27 changes: 14 additions & 13 deletions cmdeploy/src/cmdeploy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,20 +396,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",
Expand Down
3 changes: 3 additions & 0 deletions cmdeploy/src/cmdeploy/cmdeploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ def run_cmd(args, out):
kwargs=dict(command="cat /var/lib/echobot/invite-link.txt"),
)
)
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")
Expand Down
83 changes: 83 additions & 0 deletions docker/chatmail_relay.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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 \
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/*

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.
59 changes: 59 additions & 0 deletions docker/docker-compose-default.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
services:
chatmail:
build:
context: ./docker
dockerfile: chatmail_relay.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: $MAIL_DOMAIN
CHANGE_KERNEL_SETTINGS: "False"
ACME_EMAIL: $ACME_EMAIL
# RECREATE_VENV: "false"
# 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:
- "80:80"
- "443:443"
- "25:25"
- "587:587"
- "143:143"
- "465:465"
- "993:993"
volumes:
## system
- /sys/fs/cgroup:/sys/fs/cgroup:rw # required for systemd
- ./:/opt/chatmail

## 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
1 change: 1 addition & 0 deletions docker/example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MAIL_DOMAIN="chat.example.com"
11 changes: 11 additions & 0 deletions docker/files/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash
set -eo pipefail

unlink /etc/nginx/sites-enabled/default || true

SETUP_CHATMAIL_SERVICE_PATH="${SETUP_CHATMAIL_SERVICE_PATH:-/lib/systemd/system/setup_chatmail.service}"

env_vars=$(printenv | cut -d= -f1 | xargs)
sed -i "s|<envs_list>|$env_vars|g" $SETUP_CHATMAIL_SERVICE_PATH

exec /lib/systemd/systemd $@
14 changes: 14 additions & 0 deletions docker/files/setup_chatmail.service
Original file line number Diff line number Diff line change
@@ -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=<envs_list>

[Install]
WantedBy=multi-user.target
78 changes: 78 additions & 0 deletions docker/files/setup_chatmail_docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/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="${PATH_TO_SSL:-/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
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" -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, reloading nginx, dovecot and postfix services."
systemctl reload 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
Copy link
Collaborator

@Keonik1 Keonik1 Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if [ "$DEBUG_COMMANDS_ENABLED" == "true" ]; then
if [ "$DEBUG_COMMANDS_ENABLED" = true ]; then

debug_commands
fi

if [ "$FORCE_REINIT_INI_FILE" == "true" ]; then
Copy link
Collaborator

@Keonik1 Keonik1 Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if [ "$FORCE_REINIT_INI_FILE" == "true" ]; then
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
if [ "$RECREATE_VENV" == "true" ]; then
Copy link
Collaborator

@Keonik1 Keonik1 Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if [ "$RECREATE_VENV" == "true" ]; then
if [ "$RECREATE_VENV" = true ]; then

rm -rf venv
fi
./scripts/initenv.sh

./scripts/cmdeploy init --config "${INI_FILE}" $INI_CMD_ARGS $MAIL_DOMAIN
Copy link
Collaborator

@Keonik1 Keonik1 Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
./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 docker
Copy link
Collaborator

@Keonik1 Keonik1 Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
./scripts/cmdeploy run --ssh-host docker
./scripts/cmdeploy run --ssh-host @docker


echo "ForwardToConsole=yes" >> /etc/systemd/journald.conf
systemctl restart systemd-journald

monitor_certificates &
Loading