mirror of
https://github.com/chatmail/relay.git
synced 2026-05-11 16:34:39 +00:00
Compare commits
25 Commits
fix-overwr
...
docker-dns
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
950b0ffcb3 | ||
|
|
7d99dfc0fd | ||
|
|
eaff94d586 | ||
|
|
7aa9f0b9eb | ||
|
|
c4f07009ed | ||
|
|
f05fc8b84c | ||
|
|
52d04448f2 | ||
|
|
d2ff812727 | ||
|
|
929383df88 | ||
|
|
c372c55c88 | ||
|
|
e1ca74ef9f | ||
|
|
f027afdd28 | ||
|
|
5dcb002bc6 | ||
|
|
d5329fadc0 | ||
|
|
1b3f419384 | ||
|
|
87615b62d6 | ||
|
|
4c42d0f186 | ||
|
|
4fc672c3c4 | ||
|
|
dc6d8b4cf2 | ||
|
|
9037409362 | ||
|
|
d545fc8f10 | ||
|
|
a01eebe2db | ||
|
|
a6e5b9e0aa | ||
|
|
b6dce619bd | ||
|
|
aea6366bb3 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -170,3 +170,4 @@ chatmail.zone
|
||||
/custom/
|
||||
docker-compose.yaml
|
||||
.env
|
||||
/traefik/data/
|
||||
|
||||
@@ -33,7 +33,9 @@ class Config:
|
||||
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 @@ class Config:
|
||||
)
|
||||
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"]
|
||||
)
|
||||
|
||||
@@ -46,12 +46,11 @@ def init_cmd(args, out):
|
||||
inipath = args.inipath
|
||||
if args.inipath.exists():
|
||||
if not args.recreate_ini:
|
||||
print(f"[WARNING] Path exists, not modifying: {inipath}")
|
||||
return 1
|
||||
else:
|
||||
print(f"[WARNING] Force argument was provided, deleting config file: {inipath}")
|
||||
inipath.unlink()
|
||||
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}")
|
||||
@@ -73,7 +72,7 @@ def run_cmd_options(parser):
|
||||
parser.add_argument(
|
||||
"--ssh-host",
|
||||
dest="ssh_host",
|
||||
help="specify an SSH host to deploy to; uses mail_domain from chatmail.ini by default",
|
||||
help="Deploy to 'localhost', via 'docker', or to a specific SSH host",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-dns-check",
|
||||
@@ -100,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 in ["docker", "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
|
||||
@@ -132,6 +135,11 @@ def dns_cmd_options(parser):
|
||||
default=None,
|
||||
help="write out a zonefile",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ssh-host",
|
||||
dest="ssh_host",
|
||||
help="Run the DNS queries on 'localhost', in the chatmail 'docker' container, or on a specific SSH host",
|
||||
)
|
||||
|
||||
|
||||
def dns_cmd(args, out):
|
||||
@@ -363,6 +371,11 @@ 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"
|
||||
elif host == "docker":
|
||||
return "docker"
|
||||
|
||||
print(f"[ssh] login to {host}")
|
||||
return SSHExec(host, verbose=args.verbose)
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@ from . import remote
|
||||
|
||||
|
||||
def get_initial_remote_data(sshexec, mail_domain):
|
||||
if sshexec == "docker":
|
||||
return remote.rdns.perform_initial_checks(mail_domain, pre_command="docker exec chatmail ")
|
||||
elif sshexec == "localhost":
|
||||
return remote.rdns.perform_initial_checks(mail_domain, pre_command="")
|
||||
return sshexec.logged(
|
||||
call=remote.rdns.perform_initial_checks, kwargs=dict(mail_domain=mail_domain)
|
||||
)
|
||||
@@ -44,14 +48,17 @@ def check_full_zone(sshexec, remote_data, out, zonefile) -> int:
|
||||
"""Check existing DNS records, optionally write them to zone file
|
||||
and return (exitcode, remote_data) tuple."""
|
||||
|
||||
required_diff, recommended_diff = sshexec.logged(
|
||||
remote.rdns.check_zonefile,
|
||||
kwargs=dict(zonefile=zonefile, mail_domain=remote_data["mail_domain"]),
|
||||
)
|
||||
if sshexec in ["docker", "localhost"]:
|
||||
required_diff, recommended_diff = remote.rdns.check_zonefile(zonefile, remote_data["mail_domain"], verbose=False)
|
||||
else:
|
||||
required_diff, recommended_diff = sshexec.logged(
|
||||
remote.rdns.check_zonefile,
|
||||
kwargs=dict(zonefile=zonefile, mail_domain=remote_data["mail_domain"]),
|
||||
)
|
||||
|
||||
returncode = 0
|
||||
if required_diff:
|
||||
out.red("Please set required DNS entries at your DNS provider:\n")
|
||||
out.red("\nPlease set required DNS entries at your DNS provider:\n")
|
||||
for line in required_diff:
|
||||
out(line)
|
||||
out("")
|
||||
|
||||
@@ -12,23 +12,23 @@ All functions of this module
|
||||
|
||||
import re
|
||||
|
||||
from .rshell import CalledProcessError, shell
|
||||
from .rshell import CalledProcessError, shell, log_progress
|
||||
|
||||
|
||||
def perform_initial_checks(mail_domain):
|
||||
def perform_initial_checks(mail_domain, pre_command=""):
|
||||
"""Collecting initial DNS settings."""
|
||||
assert mail_domain
|
||||
if not shell("dig", fail_ok=True):
|
||||
shell("apt-get update && apt-get install -y dnsutils")
|
||||
if not shell("dig", fail_ok=True, print=log_progress):
|
||||
shell("apt-get update && apt-get install -y dnsutils", print=log_progress)
|
||||
A = query_dns("A", mail_domain)
|
||||
AAAA = query_dns("AAAA", mail_domain)
|
||||
MTA_STS = query_dns("CNAME", f"mta-sts.{mail_domain}")
|
||||
WWW = query_dns("CNAME", f"www.{mail_domain}")
|
||||
|
||||
res = dict(mail_domain=mail_domain, A=A, AAAA=AAAA, MTA_STS=MTA_STS, WWW=WWW)
|
||||
res["acme_account_url"] = shell("acmetool account-url", fail_ok=True)
|
||||
res["acme_account_url"] = shell(pre_command + "acmetool account-url", fail_ok=True, print=log_progress)
|
||||
res["dkim_entry"], res["web_dkim_entry"] = get_dkim_entry(
|
||||
mail_domain, dkim_selector="opendkim"
|
||||
mail_domain, pre_command, dkim_selector="opendkim"
|
||||
)
|
||||
|
||||
if not MTA_STS or not WWW or (not A and not AAAA):
|
||||
@@ -40,11 +40,12 @@ def perform_initial_checks(mail_domain):
|
||||
return res
|
||||
|
||||
|
||||
def get_dkim_entry(mail_domain, dkim_selector):
|
||||
def get_dkim_entry(mail_domain, pre_command, dkim_selector):
|
||||
try:
|
||||
dkim_pubkey = shell(
|
||||
f"openssl rsa -in /etc/dkimkeys/{dkim_selector}.private "
|
||||
"-pubout 2>/dev/null | awk '/-/{next}{printf(\"%s\",$0)}'"
|
||||
f"{pre_command} openssl rsa -in /etc/dkimkeys/{dkim_selector}.private "
|
||||
"-pubout 2>/dev/null | awk '/-/{next}{printf(\"%s\",$0)}'",
|
||||
print=log_progress
|
||||
)
|
||||
except CalledProcessError:
|
||||
return
|
||||
@@ -61,7 +62,7 @@ def query_dns(typ, domain):
|
||||
# Get autoritative nameserver from the SOA record.
|
||||
soa_answers = [
|
||||
x.split()
|
||||
for x in shell(f"dig -r -q {domain} -t SOA +noall +authority +answer").split(
|
||||
for x in shell(f"dig -r -q {domain} -t SOA +noall +authority +answer", print=log_progress).split(
|
||||
"\n"
|
||||
)
|
||||
]
|
||||
@@ -71,13 +72,13 @@ def query_dns(typ, domain):
|
||||
ns = soa[0][4]
|
||||
|
||||
# Query authoritative nameserver directly to bypass DNS cache.
|
||||
res = shell(f"dig @{ns} -r -q {domain} -t {typ} +short")
|
||||
res = shell(f"dig @{ns} -r -q {domain} -t {typ} +short", print=log_progress)
|
||||
if res:
|
||||
return res.split("\n")[0]
|
||||
return ""
|
||||
|
||||
|
||||
def check_zonefile(zonefile, mail_domain):
|
||||
def check_zonefile(zonefile, mail_domain, verbose=True):
|
||||
"""Check expected zone file entries."""
|
||||
required = True
|
||||
required_diff = []
|
||||
@@ -89,7 +90,7 @@ def check_zonefile(zonefile, mail_domain):
|
||||
continue
|
||||
if not zf_line.strip() or zf_line.startswith(";"):
|
||||
continue
|
||||
print(f"dns-checking {zf_line!r}")
|
||||
print(f"dns-checking {zf_line!r}") if verbose else log_progress("")
|
||||
zf_domain, zf_typ, zf_value = zf_line.split(maxsplit=2)
|
||||
zf_domain = zf_domain.rstrip(".")
|
||||
zf_value = zf_value.strip()
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
from subprocess import DEVNULL, CalledProcessError, check_output
|
||||
import sys
|
||||
|
||||
|
||||
def shell(command, fail_ok=False):
|
||||
def log_progress(data):
|
||||
sys.stderr.write(".")
|
||||
sys.stderr.flush()
|
||||
|
||||
|
||||
def shell(command, fail_ok=False, print=print):
|
||||
print(f"$ {command}")
|
||||
args = dict(shell=True)
|
||||
if fail_ok:
|
||||
|
||||
@@ -70,10 +70,6 @@ class SSHExec:
|
||||
raise self.FuncError(data)
|
||||
|
||||
def logged(self, call, kwargs):
|
||||
def log_progress(data):
|
||||
sys.stderr.write(".")
|
||||
sys.stderr.flush()
|
||||
|
||||
title = call.__doc__
|
||||
if not title:
|
||||
title = call.__name__
|
||||
@@ -82,6 +78,6 @@ class SSHExec:
|
||||
return self(call, kwargs, log_callback=print_stderr)
|
||||
else:
|
||||
print_stderr(title, end="")
|
||||
res = self(call, kwargs, log_callback=log_progress)
|
||||
res = self(call, kwargs, log_callback=remote.rshell.log_progress)
|
||||
print_stderr()
|
||||
return res
|
||||
|
||||
@@ -27,6 +27,3 @@ class TestCmdline:
|
||||
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()
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -20,11 +20,11 @@ services:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
environment:
|
||||
MAIL_DOMAIN: <your_domain>
|
||||
MAIL_DOMAIN: $MAIL_DOMAIN
|
||||
CHANGE_KERNEL_SETTINGS: "False"
|
||||
ACME_EMAIL: <your_email>
|
||||
|
||||
MAX_MESSAGE_SIZE: "50M"
|
||||
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"
|
||||
@@ -32,16 +32,17 @@ services:
|
||||
# CERTS_MONITORING_TIMEOUT: 10
|
||||
# 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
|
||||
- ./:/opt/chatmail
|
||||
- ./data/acme:/var/lib/acme
|
||||
|
||||
## data
|
||||
- ./data/chatmail:/home
|
||||
|
||||
@@ -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
|
||||
@@ -26,9 +26,10 @@ 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
|
||||
PATH_TO_SSL: "${CERTS_ROOT_DIR_CONTAINER}/${MAIL_DOMAIN}"
|
||||
ENABLE_CERTS_MONITORING: "true"
|
||||
# CERTS_MONITORING_TIMEOUT: 60
|
||||
# IS_DEVELOPMENT_INSTANCE: "true"
|
||||
@@ -36,12 +37,13 @@ services:
|
||||
- "25:25"
|
||||
- "587:587"
|
||||
- "143:143"
|
||||
- "465:465"
|
||||
- "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
|
||||
- ${CERTS_ROOT_DIR_HOST}:${CERTS_ROOT_DIR_CONTAINER}:ro
|
||||
|
||||
## data
|
||||
- ./data/chatmail:/home
|
||||
@@ -67,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 &&
|
||||
chown 0:0 ./acme.json &&
|
||||
chmod 600 ./acme.json'
|
||||
volumes:
|
||||
- ./traefik/data:/app
|
||||
|
||||
traefik:
|
||||
image: traefik:v3.3
|
||||
container_name: traefik
|
||||
@@ -77,17 +95,20 @@ services:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
command:
|
||||
- --configFile=/config.yaml
|
||||
- "--configFile=/config.yaml"
|
||||
- "--certificatesresolvers.letsEncrypt.acme.email=${ACME_EMAIL}"
|
||||
# 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
|
||||
@@ -110,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
|
||||
|
||||
@@ -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_CONTAINER="/var/lib/acme/live/${MAIL_DOMAIN}"
|
||||
CERTS_ROOT_DIR_HOST="./traefik/data/letsencrypt/certs"
|
||||
CERTS_ROOT_DIR_CONTAINER="/var/lib/acme/live"
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
unlink /etc/nginx/sites-enabled/default || true
|
||||
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -4,8 +4,9 @@ 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"}
|
||||
|
||||
if [ -z "$MAIL_DOMAIN" ]; then
|
||||
echo "ERROR: Environment variable 'MAIL_DOMAIN' must be set!" >&2
|
||||
@@ -19,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() {
|
||||
@@ -35,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
|
||||
@@ -61,12 +62,15 @@ 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
|
||||
bash /update_ini.sh
|
||||
|
||||
./scripts/cmdeploy run --ssh-host localhost --skip-dns-check
|
||||
./scripts/cmdeploy run --ssh-host docker
|
||||
|
||||
echo "ForwardToConsole=yes" >> /etc/systemd/journald.conf
|
||||
systemctl restart systemd-journald
|
||||
|
||||
@@ -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.
|
||||
@@ -48,13 +47,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,14 +63,15 @@ 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`)
|
||||
- `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}`)
|
||||
- `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'`)
|
||||
|
||||
@@ -79,113 +81,12 @@ 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
|
||||
docker compose build
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Additional steps for configuring with traefik</summary>
|
||||
|
||||
> [!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 ../..
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
7. Start docker compose and wait for the installation to finish:
|
||||
|
||||
```shell
|
||||
@@ -195,6 +96,11 @@ 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.
|
||||
|
||||
9. To send messages to other chatmail relays,
|
||||
you need to set additional DNS records.
|
||||
Run `docker exec chatmail scripts/cmdeploy.sh dns --ssh-host localhost`
|
||||
to see recommended DNS records and check whether they are correct.
|
||||
|
||||
## 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.
|
||||
@@ -269,10 +175,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() {
|
||||
@@ -288,8 +194,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
|
||||
|
||||
@@ -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 и в первом приближении оказалось сделать проще так, чем переписывать всю систему развертывания.
|
||||
@@ -43,25 +42,28 @@ 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`)
|
||||
- `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'`)
|
||||
|
||||
@@ -70,108 +72,11 @@ 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
|
||||
docker compose build
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Дополнительные шаги для конфигурации работы с traefik</summary>
|
||||
|
||||
> [!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 ../..
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
7. Запустить docker compose и дождаться завершения установки
|
||||
```shell
|
||||
docker compose up -d # запуск сервиса
|
||||
@@ -244,10 +149,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() {
|
||||
@@ -263,8 +168,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
|
||||
|
||||
33
traefik/config.yaml
Normal file
33
traefik/config.yaml
Normal file
@@ -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
|
||||
4
traefik/dynamic-configs/insecure.yaml
Normal file
4
traefik/dynamic-configs/insecure.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
http:
|
||||
serversTransports:
|
||||
insecure:
|
||||
insecureSkipVerify: true
|
||||
15
traefik/post-hook.sh
Executable file
15
traefik/post-hook.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
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
|
||||
fi
|
||||
if [ -f "privatekey.key" ]; then
|
||||
ln -sf privatekey.key privkey
|
||||
fi
|
||||
cd -
|
||||
done
|
||||
Reference in New Issue
Block a user