fix: change config to work also on debian 11

This commit is contained in:
Omid Zamani
2026-01-02 18:55:48 +01:00
parent 68277cc929
commit 928b41aae2
11 changed files with 146 additions and 28 deletions

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.11

View File

@@ -108,6 +108,13 @@ class AcmetoolDeployer(Deployer):
self.need_restart_reconcile_timer = reconcile_timer_file.changed self.need_restart_reconcile_timer = reconcile_timer_file.changed
def activate(self): def activate(self):
systemd.service(
name="Stop nginx to free port 80 for acmetool",
service="nginx.service",
running=False,
enabled=True,
)
systemd.service( systemd.service(
name="Setup acmetool-redirector service", name="Setup acmetool-redirector service",
service="acmetool-redirector.service", service="acmetool-redirector.service",
@@ -135,6 +142,15 @@ class AcmetoolDeployer(Deployer):
) )
self.need_restart_reconcile_timer = False self.need_restart_reconcile_timer = False
# Add the first domain to /etc/hosts to help acmetool's self-test
# bypass external firewalls or split-brain DNS issues.
server.shell(
name=f"Add {self.domains[0]} to /etc/hosts for self-test",
commands=[
f"grep -q ' {self.domains[0]}$' /etc/hosts || echo '127.0.0.1 {self.domains[0]}' >> /etc/hosts"
],
)
server.shell( server.shell(
name=f"Reconcile certificates for: {', '.join(self.domains)}", name=f"Reconcile certificates for: {', '.join(self.domains)}",
commands=["acmetool --batch --xlog.severity=debug reconcile"], commands=["acmetool --batch --xlog.severity=debug reconcile"],

View File

@@ -80,14 +80,22 @@ def _install_remote_venv_with_chatmaild() -> None:
remote_dist_file = f"{remote_base_dir}/dist/{dist_file.name}" remote_dist_file = f"{remote_base_dir}/dist/{dist_file.name}"
remote_venv_dir = f"{remote_base_dir}/venv" remote_venv_dir = f"{remote_base_dir}/venv"
root_owned = dict(user="root", group="root", mode="644") root_owned = dict(user="root", group="root", mode="644")
root_owned_dir = dict(user="root", group="root", mode="755")
uv_prefix = "UV_PYTHON_INSTALL_DIR=/usr/local/share/uv/python"
server.shell( server.shell(
name="Install uv", name="Install uv globally if not present",
commands=[ commands=[
"curl -LsSf https://astral.sh/uv/install.sh | INSTALLER_NO_MODIFY_PATH=1 sh", "command -v uv >/dev/null 2>&1 || (curl -LsSf https://astral.sh/uv/install.sh | INSTALLER_NO_MODIFY_PATH=1 sudo sh -s -- --install-dir /usr/local/bin)",
], ],
) )
files.directory(
name="Ensure shared uv python directory exists",
path="/usr/local/share/uv/python",
**root_owned_dir,
)
files.put( files.put(
name="Upload chatmaild source package", name="Upload chatmaild source package",
src=dist_file.open("rb"), src=dist_file.open("rb"),
@@ -95,10 +103,30 @@ def _install_remote_venv_with_chatmaild() -> None:
create_remote_dir=True, create_remote_dir=True,
**root_owned, **root_owned,
) )
# Ensure parent directory is accessible
files.directory(
name=f"Ensure {remote_base_dir} is accessible",
path=remote_base_dir,
**root_owned_dir,
)
files.directory(
name=f"Ensure {remote_base_dir}/dist is accessible",
path=f"{remote_base_dir}/dist",
**root_owned_dir,
)
server.shell( server.shell(
name=f"chatmaild virtualenv {remote_venv_dir}", name=f"chatmaild virtualenv {remote_venv_dir}",
commands=[f"/root/.local/bin/uv venv {remote_venv_dir} --allow-existing"], commands=[f"{uv_prefix} uv venv {remote_venv_dir} --python 3.11 --allow-existing"],
)
# Ensure venv and managed pythons are accessible by other users (like www-data)
server.shell(
name="Make venv and managed pythons accessible",
commands=[
f"chmod -R a+rX {remote_venv_dir}",
"chmod -R a+rX /usr/local/share/uv/python || true",
],
) )
apt.packages( apt.packages(
@@ -109,7 +137,7 @@ def _install_remote_venv_with_chatmaild() -> None:
server.shell( server.shell(
name=f"forced uv-pip-install {dist_file.name}", name=f"forced uv-pip-install {dist_file.name}",
commands=[ commands=[
f"/root/.local/bin/uv pip install --python {remote_venv_dir}/bin/python --force-reinstall {remote_dist_file}" f"{uv_prefix} uv pip install --python {remote_venv_dir}/bin/python --force-reinstall {remote_dist_file}"
], ],
) )
@@ -448,6 +476,10 @@ class ChatmailDeployer(Deployer):
self.mail_domain = mail_domain self.mail_domain = mail_domain
def install(self): def install(self):
server.shell(
name="Fix broken packages",
commands=["apt-get install -f -y"],
)
apt.update(name="apt update", cache_time=24 * 3600) apt.update(name="apt update", cache_time=24 * 3600)
apt.upgrade(name="upgrade apt packages", auto_remove=True) apt.upgrade(name="upgrade apt packages", auto_remove=True)

View File

@@ -1,6 +1,6 @@
from chatmaild.config import Config from chatmaild.config import Config
from pyinfra import host from pyinfra import host
from pyinfra.facts.server import Arch, Sysctl from pyinfra.facts.server import Arch, LinuxDistribution, Sysctl
from pyinfra.facts.systemd import SystemdEnabled from pyinfra.facts.systemd import SystemdEnabled
from pyinfra.operations import apt, files, server, systemd from pyinfra.operations import apt, files, server, systemd
@@ -50,6 +50,33 @@ class DovecotDeployer(Deployer):
def _install_dovecot_package(package: str, arch: str): def _install_dovecot_package(package: str, arch: str):
distro = host.get_fact(LinuxDistribution)
is_old_debian = False
if distro:
distro_id = str(distro.get("id", "")).lower()
distro_name = str(distro.get("name", "")).lower()
major_version = str(distro.get("major_version", "0"))
if "debian" in distro_id or "debian" in distro_name:
try:
# Handle cases where major_version might be '11.13'
m_ver = major_version.split(".")[0]
if m_ver.isdigit() and 0 < int(m_ver) < 12:
is_old_debian = True
except (ValueError, TypeError, IndexError):
pass
# Fallback for systems where major_version might not be parsed correctly but name contains 'bullseye'
if "bullseye" in distro_name or "buster" in distro_name:
is_old_debian = True
if is_old_debian:
apt.packages(
name=f"Install system dovecot-{package}",
packages=[f"dovecot-{package}"],
update=True,
)
return
arch = "amd64" if arch == "x86_64" else arch arch = "amd64" if arch == "x86_64" else arch
arch = "arm64" if arch == "aarch64" else arch arch = "arm64" if arch == "aarch64" else arch
url = f"https://download.delta.chat/dovecot/dovecot-{package}_2.3.21%2Bdfsg1-3_{arch}.deb" url = f"https://download.delta.chat/dovecot/dovecot-{package}_2.3.21%2Bdfsg1-3_{arch}.deb"
@@ -80,7 +107,18 @@ def _install_dovecot_package(package: str, arch: str):
cache_time=60 * 60 * 24 * 365 * 10, # never redownload the package cache_time=60 * 60 * 24 * 365 * 10, # never redownload the package
) )
apt.deb(name=f"Install dovecot-{package}", src=deb_filename) # Use a shell command to try installing the custom .deb and fallback if it fails.
# This prevents the whole deployment from failing if custom dovecot is not compatible.
server.shell(
name=f"Install dovecot-{package} (custom or system fallback)",
commands=[
f"if ! dpkg -i {deb_filename}; then "
f"echo 'HEY: dovecot the custom is not installed, we install your os main one' >&2; "
f"DEBIAN_FRONTEND=noninteractive apt-get install -f -y; "
f"DEBIAN_FRONTEND=noninteractive apt-get install -y dovecot-{package}; "
f"fi"
],
)
def _configure_dovecot(config: Config, debug: bool = False) -> (bool, bool): def _configure_dovecot(config: Config, debug: bool = False) -> (bool, bool):

View File

@@ -229,7 +229,7 @@ ssl = required
ssl_cert = </var/lib/acme/live/{{ config.mail_domain }}/fullchain ssl_cert = </var/lib/acme/live/{{ config.mail_domain }}/fullchain
ssl_key = </var/lib/acme/live/{{ config.mail_domain }}/privkey ssl_key = </var/lib/acme/live/{{ config.mail_domain }}/privkey
ssl_dh = </usr/share/dovecot/dh.pem ssl_dh = </usr/share/dovecot/dh.pem
ssl_min_protocol = TLSv1.3 ssl_min_protocol = TLSv1.2
ssl_prefer_server_ciphers = yes ssl_prefer_server_ciphers = yes

View File

@@ -1,5 +1,5 @@
from chatmaild.config import Config from chatmaild.config import Config
from pyinfra.operations import apt, files, systemd from pyinfra.operations import apt, files, server, systemd
from cmdeploy.basedeploy import ( from cmdeploy.basedeploy import (
Deployer, Deployer,
@@ -43,6 +43,13 @@ class NginxDeployer(Deployer):
name="Install nginx", name="Install nginx",
packages=["nginx", "libnginx-mod-stream"], packages=["nginx", "libnginx-mod-stream"],
) )
server.shell(
name="Remove default nginx configuration",
commands=[
"rm -f /etc/nginx/sites-enabled/default",
"rm -f /etc/nginx/conf.d/default.conf",
],
)
files.file("/usr/sbin/policy-rc.d", present=False) files.file("/usr/sbin/policy-rc.d", present=False)

View File

@@ -26,8 +26,8 @@ smtp_tls_security_level=verify
smtp_tls_servername = hostname smtp_tls_servername = hostname
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtp_tls_policy_maps = inline:{nauta.cu=may} smtp_tls_policy_maps = inline:{nauta.cu=may}
smtp_tls_protocols = >=TLSv1.2 smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_mandatory_protocols = >=TLSv1.2 smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
# Disable anonymous cipher suites # Disable anonymous cipher suites
# and known insecure algorithms. # and known insecure algorithms.

View File

@@ -10,17 +10,15 @@
# (yes) (yes) (no) (never) (100) # (yes) (yes) (no) (never) (100)
# ========================================================================== # ==========================================================================
{% if debug == true %} {% if debug == true %}
smtp inet n - y - - smtpd -v smtp inet n - n - - smtpd
{%- else %}
smtp inet n - y - - smtpd
{%- endif %} {%- endif %}
-o smtpd_tls_security_level=encrypt -o smtpd_tls_security_level=encrypt
-o smtpd_tls_mandatory_protocols=>=TLSv1.2 -o smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3,!TLSv1,!TLSv1.1
-o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port_incoming }} -o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port_incoming }}
submission inet n - y - 5000 smtpd submission inet n - n - 5000 smtpd
-o syslog_name=postfix/submission -o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt -o smtpd_tls_security_level=encrypt
-o smtpd_tls_mandatory_protocols=>=TLSv1.3 -o smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3,!TLSv1,!TLSv1.1
-o smtpd_sasl_auth_enable=yes -o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot -o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth -o smtpd_sasl_path=private/auth
@@ -33,11 +31,11 @@ submission inet n - y - 5000 smtpd
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o smtpd_client_connection_count_limit=1000 -o smtpd_client_connection_count_limit=1000
-o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port }} -o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port }}
smtps inet n - y - 5000 smtpd smtps inet n - n - 5000 smtpd
-o syslog_name=postfix/smtps -o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes -o smtpd_tls_wrappermode=yes
-o smtpd_tls_security_level=encrypt -o smtpd_tls_security_level=encrypt
-o smtpd_tls_mandatory_protocols=>=TLSv1.3 -o smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3,!TLSv1,!TLSv1.1
-o smtpd_sasl_auth_enable=yes -o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot -o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth -o smtpd_sasl_path=private/auth

View File

@@ -8,7 +8,7 @@ sudo apt install -y git curl wget python3-dev gcc python3 nano sed
# 1.1 Install uv # 1.1 Install uv
export PATH="$HOME/.local/bin:/root/.local/bin:$PATH" export PATH="$HOME/.local/bin:/root/.local/bin:$PATH"
if ! command -v uv &> /dev/null; then if ! command -v uv > /dev/null 2>&1; then
if [ -f "/root/.local/bin/uv" ]; then if [ -f "/root/.local/bin/uv" ]; then
export PATH="/root/.local/bin:$PATH" export PATH="/root/.local/bin:$PATH"
elif [ -f "$HOME/.local/bin/uv" ]; then elif [ -f "$HOME/.local/bin/uv" ]; then
@@ -16,7 +16,7 @@ if ! command -v uv &> /dev/null; then
fi fi
fi fi
if ! command -v uv &> /dev/null; then if ! command -v uv > /dev/null 2>&1; then
echo "--- Installing uv ---" echo "--- Installing uv ---"
curl -LsSf https://astral.sh/uv/install.sh | sh curl -LsSf https://astral.sh/uv/install.sh | sh
# Ensure uv is in PATH for the current script # Ensure uv is in PATH for the current script
@@ -43,7 +43,8 @@ read -p "Enter your email for ACME/Let's Encrypt: " ACME_EMAIL
# 5. Initialize configuration # 5. Initialize configuration
echo "--- Initializing chatmail configuration ---" echo "--- Initializing chatmail configuration ---"
./scripts/cmdeploy init "$MAIL_DOMAIN" ./scripts/cmdeploy init "$MAIL_DOMAIN" || true
# 6. Modify chatmail.ini with specific requirements # 6. Modify chatmail.ini with specific requirements
echo "--- Customizing chatmail.ini ---" echo "--- Customizing chatmail.ini ---"

29
pyproject.toml Normal file
View File

@@ -0,0 +1,29 @@
[project]
name = "relay-ir"
version = "0.1.0"
description = "Chatmail relay workspace"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"chatmaild",
"cmdeploy",
"sphinx",
"sphinxcontrib-mermaid",
"sphinx-autobuild",
"furo",
]
[tool.uv]
managed = true
package = false
[tool.uv.workspace]
members = ["chatmaild", "cmdeploy"]
[tool.uv.sources]
chatmaild = { workspace = true }
cmdeploy = { workspace = true }
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"

View File

@@ -21,7 +21,7 @@ fi
# Ensure uv is in PATH # Ensure uv is in PATH
export PATH="$HOME/.local/bin:/root/.local/bin:$PATH" export PATH="$HOME/.local/bin:/root/.local/bin:$PATH"
if ! command -v uv &> /dev/null; then if ! command -v uv > /dev/null 2>&1; then
if [ -f "/root/.local/bin/uv" ]; then if [ -f "/root/.local/bin/uv" ]; then
export PATH="/root/.local/bin:$PATH" export PATH="/root/.local/bin:$PATH"
elif [ -f "$HOME/.local/bin/uv" ]; then elif [ -f "$HOME/.local/bin/uv" ]; then
@@ -29,13 +29,9 @@ if ! command -v uv &> /dev/null; then
fi fi
fi fi
if ! command -v uv &> /dev/null; then if ! command -v uv > /dev/null 2>&1; then
echo "uv not found. Please install it first or run init.sh" echo "uv not found. Please install it first or run init.sh"
exit 1 exit 1
fi fi
uv venv venv uv sync --python 3.11
uv pip install -e chatmaild
uv pip install -e cmdeploy
uv pip install sphinx sphinxcontrib-mermaid sphinx-autobuild furo # for building the docs