From 0c746553b32bb1839c7018739ed58b73cb9c84fa Mon Sep 17 00:00:00 2001 From: j4n Date: Mon, 16 Feb 2026 15:22:08 +0100 Subject: [PATCH] docker: move cmdeploy into docker image --- docker-compose.yaml | 8 +- docker/chatmail_relay.dockerfile | 30 ++++---- docker/example.env | 1 - docker/files/setup_chatmail_docker.sh | 35 +++------ docs/DOCKER_INSTALLATION_EN.md | 103 ++++++++++++++++---------- 5 files changed, 92 insertions(+), 85 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 92c13562..c0e57477 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -23,7 +23,7 @@ services: CHANGE_KERNEL_SETTINGS: "False" MAIL_DOMAIN: $MAIL_DOMAIN ACME_EMAIL: $ACME_EMAIL - RECREATE_VENV: $RECREATE_VENV + WWW_FOLDER: /opt/chatmail-www MAX_MESSAGE_SIZE: $MAX_MESSAGE_SIZE DEBUG_COMMANDS_ENABLED: $DEBUG_COMMANDS_ENABLED FORCE_REINIT_INI_FILE: $FORCE_REINIT_INI_FILE @@ -36,15 +36,15 @@ services: 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-acme:/var/lib/acme - ## custom resources - # - ./custom/www/src/index.md:/opt/chatmail/www/src/index.md + ## optional overrides + # - ./chatmail.ini:/etc/chatmail/chatmail.ini # use your own config + # - ./custom/www:/opt/chatmail-www # custom website ## debug # - ./docker/files/setup_chatmail_docker.sh:/setup_chatmail_docker.sh diff --git a/docker/chatmail_relay.dockerfile b/docker/chatmail_relay.dockerfile index 13322c6e..9a0e352e 100644 --- a/docker/chatmail_relay.dockerfile +++ b/docker/chatmail_relay.dockerfile @@ -54,32 +54,30 @@ RUN apt-get update && \ WORKDIR /opt/chatmail -# --- Build-time install stage --- -# Bake the "install" deployer stage into the image; we can't use -# scripts/initenv.sh because /opt/chatmail is empty at build time as -# source arrives at runtime via volume mount., so we use a throwaway venv. +# --- Build-time: install cmdeploy venv and run install stage --- +# Editable install so importlib.resources reads directly from the source tree. # On container start only "configure,activate" stages run. -COPY . /tmp/chatmail-src/ -WORKDIR /tmp/chatmail-src +COPY . /opt/chatmail/ +WORKDIR /opt/chatmail -# Dummy config — deploy_chatmail() needs a parseable ini to instantiate deployers RUN printf '[params]\nmail_domain = build.local\n' > /tmp/chatmail.ini -# Do what initenv.sh would do without the docs -RUN python3 -m venv /tmp/build-venv && \ - /tmp/build-venv/bin/pip install --no-cache-dir \ - -e chatmaild -e cmdeploy +RUN python3 -m venv /opt/cmdeploy && \ + /opt/cmdeploy/bin/pip install --no-cache-dir \ + -e chatmaild/ -e cmdeploy/ RUN CMDEPLOY_STAGES=install \ CHATMAIL_INI=/tmp/chatmail.ini \ CHATMAIL_DOCKER=True \ - /tmp/build-venv/bin/pyinfra @local \ - /tmp/chatmail-src/cmdeploy/src/cmdeploy/run.py -y + /opt/cmdeploy/bin/pyinfra @local \ + /opt/chatmail/cmdeploy/src/cmdeploy/run.py -y -RUN rm -rf /tmp/chatmail-src /tmp/build-venv /tmp/chatmail.ini +RUN cp -a www/ /opt/chatmail-www/ -WORKDIR /opt/chatmail -# --- End build-time install stage --- +RUN rm -f /tmp/chatmail.ini +# --- End build-time install --- + +ENV CHATMAIL_INI=/etc/chatmail/chatmail.ini ARG SETUP_CHATMAIL_SERVICE_PATH=/lib/systemd/system/setup_chatmail.service COPY ./docker/files/setup_chatmail.service "$SETUP_CHATMAIL_SERVICE_PATH" diff --git a/docker/example.env b/docker/example.env index 4aa8e2dc..c5263123 100644 --- a/docker/example.env +++ b/docker/example.env @@ -1,6 +1,5 @@ MAIL_DOMAIN="chat.example.com" # ACME_EMAIL="" -# RECREATE_VENV="false" # MAX_MESSAGE_SIZE="50M" # DEBUG_COMMANDS_ENABLED="true" # FORCE_REINIT_INI_FILE="true" diff --git a/docker/files/setup_chatmail_docker.sh b/docker/files/setup_chatmail_docker.sh index f3ac4277..27b5bc67 100755 --- a/docker/files/setup_chatmail_docker.sh +++ b/docker/files/setup_chatmail_docker.sh @@ -1,24 +1,19 @@ #!/bin/bash set -eo pipefail -export INI_FILE="${INI_FILE:-chatmail.ini}" +export CHATMAIL_INI="${CHATMAIL_INI:-/etc/chatmail/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"} + +CMDEPLOY=/opt/cmdeploy/bin/cmdeploy 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}' } @@ -48,11 +43,7 @@ monitor_certificates() { ### MAIN -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,21 +53,13 @@ fi 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 - rm -rf venv -fi -# Skip venv creation if it already exists -if [ ! -x venv/bin/python ] || [ ! -x venv/bin/cmdeploy ]; then - ./scripts/initenv.sh -fi - -./scripts/cmdeploy init --config "${INI_FILE}" $INI_CMD_ARGS $MAIL_DOMAIN || true -bash /update_ini.sh +# Create chatmail.ini from env vars (skips if file already exists, e.g. volume-mounted) +mkdir -p "$(dirname "$CHATMAIL_INI")" +$CMDEPLOY init --config "$CHATMAIL_INI" $INI_CMD_ARGS $MAIL_DOMAIN || true +INI_FILE="$CHATMAIL_INI" bash /update_ini.sh export CMDEPLOY_STAGES="${CMDEPLOY_STAGES:-configure,activate}" -./scripts/cmdeploy run --ssh-host @docker +$CMDEPLOY run --ssh-host @docker echo "ForwardToConsole=yes" >> /etc/systemd/journald.conf systemctl restart systemd-journald diff --git a/docs/DOCKER_INSTALLATION_EN.md b/docs/DOCKER_INSTALLATION_EN.md index f93161a6..edd701ae 100644 --- a/docs/DOCKER_INSTALLATION_EN.md +++ b/docs/DOCKER_INSTALLATION_EN.md @@ -29,16 +29,7 @@ Please substitute it with your own domain. 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. 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: +2. 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 @@ -46,83 +37,117 @@ echo "fs.inotify.max_user_watches=65536" | sudo tee -a /etc/sysctl.d/99-inotify. sudo sysctl --system ``` -2. Copy `./docker/example.env` and rename it to `.env`. This file stores variables used in `docker-compose.yaml`. +## Building the image + +Clone the repository and build the Docker image: + +```shell +git clone https://github.com/chatmail/relay +cd relay +docker compose build chatmail +``` + +The build bakes all binaries, Python packages, and the install stage into the image. After building, only `docker-compose.yaml` and `.env` are needed to run the container. + +## Running with Docker Compose + +1. Copy `docker-compose.yaml` and `docker/example.env` into a working directory: + +```shell +cp docker-compose.yaml /path/to/your/workdir/ +cp docker/example.env /path/to/your/workdir/.env +``` + +If you are running from the cloned repo directory, just copy the env file: ```shell cp ./docker/example.env .env ``` -3. Configure environment variables in the `.env` file. These variables are used in the `docker-compose.yaml` file to pass repeated values. +2. Configure environment variables in the `.env` file. 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`) -- `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` – 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'`) +- `CERTS_MONITORING_TIMEOUT` – Interval in seconds to check if certificates have changed. (default: `60`) - `CMDEPLOY_STAGES` – Deployment stages to run on container start. (default: `"configure,activate"`). Set to `"install,configure,activate"` to force a full reinstall. 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. -4. Build the Docker image: - -```shell -docker compose build chatmail -``` - -5. Start docker compose and wait for the installation to finish: +3. Start the container: ```shell docker compose up -d # start service docker compose logs -f chatmail # view container logs, press CTRL+C to exit ``` -### venv creation -The first container start takes longer because it creates the cmdeploy Python virtualenv at `/opt/chatmail/venv` (persisted on the host via volume mount). Subsequent starts reuse the existing venv. Set `RECREATE_VENV=true` in `.env` to force a rebuild if needed. +4. After installation is complete, you can open `https://` in your browser. -6. After installation is complete, you can open `https://` in your browser. +## Managing the server -## 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. +Use `docker exec` to run cmdeploy commands inside the container: ```shell -mkdir -p ./custom +# Show required DNS records +docker exec chatmail /opt/cmdeploy/bin/cmdeploy dns --ssh-host @docker + +# Check server status +docker exec chatmail /opt/cmdeploy/bin/cmdeploy status --ssh-host @docker + +# Run benchmarks (can also run from any machine with cmdeploy installed) +docker exec chatmail /opt/cmdeploy/bin/cmdeploy bench chat.example.com ``` -2. Modify the required file. For example, `index.md`: +## Customization + +### Custom website + +You can customize the Chatmail landing page by mounting a directory with your own website source files. + +1. Create a directory with your custom website source: ```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: +2. In `docker-compose.yaml`, uncomment or add the website volume mount: ```yaml services: chatmail: volumes: ... - ## custom resources - - ./custom/www/src/index.md:/opt/chatmail/www/src/index.md + - ./custom/www:/opt/chatmail-www ``` -4. Restart the service: +3. Restart the service: ```shell docker compose down docker compose up -d ``` +### Custom chatmail.ini + +Instead of using environment variables, you can mount your own `chatmail.ini` configuration file. This is useful if you prefer managing the full ini file directly or want to share one configuration across environments. + +1. In `docker-compose.yaml`, uncomment or add the ini volume mount: + +```yaml +services: + chatmail: + volumes: + ... + - ./chatmail.ini:/etc/chatmail/chatmail.ini +``` + +2. Environment variables from `.env` are still applied on top of the mounted file at container start, so you can combine both approaches. + ## Migrating from a bare-metal install If you have an existing bare-metal Chatmail installation and want to switch to Docker: @@ -144,6 +169,8 @@ systemctl disable postfix dovecot doveauth nginx opendkim unbound acmetool-redir python3 docker/cm_ini_to_env.py /usr/local/lib/chatmaild/chatmail.ini .env ``` +or mount it (see above). + 3. Copy persistent data into the `./data/` subdirectories: ```shell