mirror of
https://github.com/chatmail/relay.git
synced 2026-05-10 16:04:37 +00:00
Add installation via docker compose (MVP 1)
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -164,3 +164,9 @@ cython_debug/
|
|||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
chatmail.zone
|
chatmail.zone
|
||||||
|
|
||||||
|
# docker
|
||||||
|
/data/
|
||||||
|
/custom/
|
||||||
|
docker-compose.yaml
|
||||||
|
.env
|
||||||
|
|||||||
@@ -121,6 +121,8 @@
|
|||||||
Provide an "fsreport" CLI for more fine grained analysis of message files.
|
Provide an "fsreport" CLI for more fine grained analysis of message files.
|
||||||
([#637](https://github.com/chatmail/relay/pull/637))
|
([#637](https://github.com/chatmail/relay/pull/637))
|
||||||
|
|
||||||
|
- 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))
|
||||||
|
|
||||||
## 1.7.0 2025-09-11
|
## 1.7.0 2025-09-11
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,19 @@ def run_cmd(args, out):
|
|||||||
else:
|
else:
|
||||||
out.red("Website deployment failed.")
|
out.red("Website deployment failed.")
|
||||||
elif retcode == 0:
|
elif retcode == 0:
|
||||||
|
if not args.disable_mail:
|
||||||
|
print("\nYou can try out the relay by talking to this echo bot: ")
|
||||||
|
sshexec = SSHExec(args.config.mail_domain, verbose=args.verbose)
|
||||||
|
print(
|
||||||
|
sshexec(
|
||||||
|
call=remote.rshell.shell,
|
||||||
|
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.")
|
out.green("Deploy completed, call `cmdeploy dns` next.")
|
||||||
elif not args.dns_check_disabled and not remote_data["acme_account_url"]:
|
elif not args.dns_check_disabled and not remote_data["acme_account_url"]:
|
||||||
out.red("Deploy completed but letsencrypt not configured")
|
out.red("Deploy completed but letsencrypt not configured")
|
||||||
|
|||||||
@@ -80,6 +80,13 @@ steps. Please substitute it with your own domain.
|
|||||||
configure at your DNS provider (it can take some time until they are
|
configure at your DNS provider (it can take some time until they are
|
||||||
public).
|
public).
|
||||||
|
|
||||||
|
Docker installation
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
We have experimental support for `docker compose <https://github.com/chatmail/relay/blob/docker-rebase/docs/DOCKER_INSTALLATION_EN.md>`_,
|
||||||
|
but it is not covered by automated tests yet,
|
||||||
|
so don't expect everything to work.
|
||||||
|
|
||||||
Other helpful commands
|
Other helpful commands
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|||||||
102
docker/chatmail_server.dockerfile
Normal file
102
docker/chatmail_server.dockerfile
Normal file
@@ -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.
|
||||||
57
docker/docker-compose-default.yaml
Normal file
57
docker/docker-compose-default.yaml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
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: <your_domain>
|
||||||
|
ACME_EMAIL: <your_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
|
||||||
3
docker/example.env
Normal file
3
docker/example.env
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
MAIL_DOMAIN="chat.example.com"
|
||||||
|
|
||||||
|
PATH_TO_SSL_CONTAINER="/var/lib/acme/live/${MAIL_DOMAIN}"
|
||||||
20
docker/files/entrypoint.sh
Executable file
20
docker/files/entrypoint.sh
Executable file
@@ -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|<envs_list>|$env_vars|g" $SETUP_CHATMAIL_SERVICE_PATH
|
||||||
|
|
||||||
|
exec /lib/systemd/systemd $@
|
||||||
14
docker/files/setup_chatmail.service
Normal file
14
docker/files/setup_chatmail.service
Normal file
@@ -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
|
||||||
73
docker/files/setup_chatmail_docker.sh
Executable file
73
docker/files/setup_chatmail_docker.sh
Executable file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/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}}"
|
||||||
|
|
||||||
|
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 &
|
||||||
79
docker/files/update_ini.sh
Normal file
79
docker/files/update_ini.sh
Normal file
@@ -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"
|
||||||
196
docs/DOCKER_INSTALLATION_EN.md
Normal file
196
docs/DOCKER_INSTALLATION_EN.md
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
# Known issues and limitations
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
1. Copy the file `./docker/docker-compose-default.yaml` 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
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
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://<your_domain_name>` 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: <image name from step 1>
|
||||||
|
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
|
||||||
|
```
|
||||||
176
docs/DOCKER_INSTALLATION_RU.md
Normal file
176
docs/DOCKER_INSTALLATION_RU.md
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# Известные проблемы и ограничения
|
||||||
|
- 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
|
||||||
|
|
||||||
|
1. Скопировать файл `./docker/docker-compose-default.yaml` в `docker-compose.yaml`. Это нужно потому что `docker-compose.yaml` находится в `.gitignore` и не будет создавать конфликты при обновлении гит репозитория.
|
||||||
|
```shell
|
||||||
|
cp ./docker/docker-compose-default.yaml docker-compose.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Запустить docker compose и дождаться завершения установки
|
||||||
|
```shell
|
||||||
|
docker compose up -d # запуск сервиса
|
||||||
|
docker compose logs -f chatmail # просмотр логов контейнера. Для выхода нажать CTRL+C
|
||||||
|
```
|
||||||
|
|
||||||
|
8. По окончанию установки можно открыть в браузер `https://<your_domain_name>`
|
||||||
|
|
||||||
|
## Использование кастомных файлов
|
||||||
|
При использовании 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: <image name from step 1>
|
||||||
|
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
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user