Compare commits

..

1 Commits

Author SHA1 Message Date
missytake
bedfc09e8e nginx: allow custom nginx config under sites-enabled
fix 562
2025-08-19 09:44:48 +02:00
6 changed files with 49 additions and 46 deletions

View File

@@ -2,6 +2,9 @@
## untagged ## untagged
- Allow custom nginx config files
([#617](https://github.com/chatmail/relay/pull/617))
- Check whether GCC is installed in initenv.sh - Check whether GCC is installed in initenv.sh
([#608](https://github.com/chatmail/relay/pull/608)) ([#608](https://github.com/chatmail/relay/pull/608))

View File

@@ -255,6 +255,19 @@ This starts a local live development cycle for chatmail web pages:
- Starts a browser window automatically where you can "refresh" as needed. - Starts a browser window automatically where you can "refresh" as needed.
#### Custom web pages
If you want to include other pages,
they need their separate nginx config
under `/etc/nginx/sites-enabled/`.
Note that they need to listen on port 8443 instead of 443.
To request TLS certificates for the corresponding domains,
point the DNS records to your Server and run `acmetool want <domain>`.
You can find the TLS certificates under `/var/lib/acme/live`.
They will be automatically renewed.
## Mailbox directory layout ## Mailbox directory layout
Fresh chatmail addresses have a mailbox directory that contains: Fresh chatmail addresses have a mailbox directory that contains:

View File

@@ -424,6 +424,12 @@ def _configure_nginx(config: Config, debug: bool = False) -> bool:
"""Configures nginx HTTP server.""" """Configures nginx HTTP server."""
need_restart = False need_restart = False
files.link(
name="disable nginx default site",
path="/etc/nginx/sites-enabled/default",
present=False,
)
main_config = files.template( main_config = files.template(
src=importlib.resources.files(__package__).joinpath("nginx/nginx.conf.j2"), src=importlib.resources.files(__package__).joinpath("nginx/nginx.conf.j2"),
dest="/etc/nginx/nginx.conf", dest="/etc/nginx/nginx.conf",

View File

@@ -19,7 +19,7 @@ from packaging import version
from termcolor import colored from termcolor import colored
from . import dns, remote from . import dns, remote
from .sshexec import SSHExec, Local from .sshexec import SSHExec
# #
# cmdeploy sub commands and options # cmdeploy sub commands and options
@@ -62,18 +62,13 @@ def run_cmd_options(parser):
"--ssh-host", "--ssh-host",
dest="ssh_host", dest="ssh_host",
help="specify an SSH host to deploy to; uses mail_domain from chatmail.ini by default", help="specify an SSH host to deploy to; uses mail_domain from chatmail.ini by default",
default=None,
) )
def run_cmd(args, out): def run_cmd(args, out):
"""Deploy chatmail services on the remote server.""" """Deploy chatmail services on the remote server."""
ssh_host = args.ssh_host if args.ssh_host else args.config.mail_domain sshexec = args.get_sshexec()
if ssh_host == "localhost":
sshexec = Local(ssh_host)
else:
sshexec = args.get_sshexec(ssh_host)
require_iroh = args.config.enable_iroh_relay require_iroh = args.config.enable_iroh_relay
remote_data = dns.get_initial_remote_data(sshexec, args.config.mail_domain) remote_data = dns.get_initial_remote_data(sshexec, args.config.mail_domain)
if not dns.check_initial_remote_data(remote_data, print=out.red): if not dns.check_initial_remote_data(remote_data, print=out.red):
@@ -85,7 +80,7 @@ def run_cmd(args, out):
env["CHATMAIL_REQUIRE_IROH"] = "True" if require_iroh else "" env["CHATMAIL_REQUIRE_IROH"] = "True" if require_iroh else ""
deploy_path = importlib.resources.files(__package__).joinpath("deploy.py").resolve() deploy_path = importlib.resources.files(__package__).joinpath("deploy.py").resolve()
pyinf = "pyinfra --dry" if args.dry_run else "pyinfra" pyinf = "pyinfra --dry" if args.dry_run else "pyinfra"
ssh_host = "@local" if ssh_host == "localhost" else f"--ssh-host {ssh_host}" 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" cmd = f"{pyinf} --ssh-user root {ssh_host} {deploy_path} -y"
if version.parse(pyinfra.__version__) < version.parse("3"): if version.parse(pyinfra.__version__) < version.parse("3"):
out.red("Please re-run scripts/initenv.sh to update pyinfra to version 3.") out.red("Please re-run scripts/initenv.sh to update pyinfra to version 3.")
@@ -335,9 +330,9 @@ def main(args=None):
if not hasattr(args, "func"): if not hasattr(args, "func"):
return parser.parse_args(["-h"]) return parser.parse_args(["-h"])
def get_sshexec(host): def get_sshexec():
print(f"[ssh] login to {host}") print(f"[ssh] login to {args.config.mail_domain}")
return SSHExec(host, verbose=args.verbose) return SSHExec(args.config.mail_domain, verbose=args.verbose)
args.get_sshexec = get_sshexec args.get_sshexec = get_sshexec

View File

@@ -136,4 +136,7 @@ http {
return 301 $scheme://{{ config.domain_name }}$request_uri; return 301 $scheme://{{ config.domain_name }}$request_uri;
access_log syslog:server=unix:/dev/log,facility=local7; access_log syslog:server=unix:/dev/log,facility=local7;
} }
# Include custom pages; they need to listen on port 8443 instead of port 443
include /etc/nginx/sites-enabled/*;
} }

View File

@@ -1,6 +1,5 @@
import inspect import inspect
import os import os
import subprocess
import sys import sys
from queue import Queue from queue import Queue
@@ -45,16 +44,30 @@ def print_stderr(item="", end="\n"):
print(item, file=sys.stderr, end=end) print(item, file=sys.stderr, end=end)
class Exec: class SSHExec:
RemoteError = execnet.RemoteError
FuncError = FuncError FuncError = FuncError
def __init__(self, host, verbose, timeout): def __init__(self, host, verbose=False, python="python3", timeout=60):
self.host = host self.gateway = execnet.makegateway(f"ssh=root@{host}//python={python}")
self._remote_cmdloop_channel = bootstrap_remote(self.gateway, remote)
self.timeout = timeout self.timeout = timeout
self.verbose = verbose self.verbose = verbose
def __call__(self, call, kwargs=None, log_callback=None): def __call__(self, call, kwargs=None, log_callback=None):
return subprocess.check_output(call) if kwargs is None:
kwargs = {}
assert call.__module__.startswith("cmdeploy.remote")
modname = call.__module__.replace("cmdeploy.", "")
self._remote_cmdloop_channel.send((modname, call.__name__, kwargs))
while 1:
code, data = self._remote_cmdloop_channel.receive(timeout=self.timeout)
if log_callback is not None and code == "log":
log_callback(data)
elif code == "finish":
return data
elif code == "error":
raise self.FuncError(data)
def logged(self, call, kwargs): def logged(self, call, kwargs):
def log_progress(data): def log_progress(data):
@@ -72,33 +85,3 @@ class Exec:
res = self(call, kwargs, log_callback=log_progress) res = self(call, kwargs, log_callback=log_progress)
print_stderr() print_stderr()
return res return res
class Local(Exec):
def __init__(self, host, verbose=False, timeout=60):
super().__init__(host, verbose, timeout)
class SSHExec(Exec):
RemoteError = execnet.RemoteError
def __init__(self, host, verbose=False, timeout=60):
super().__init__(host, verbose, timeout)
self.gateway = execnet.makegateway(f"ssh=root@{host}//python=python3")
self._remote_cmdloop_channel = bootstrap_remote(self.gateway, remote)
def __call__(self, call, kwargs=None, log_callback=None):
if kwargs is None:
kwargs = {}
assert call.__module__.startswith("cmdeploy.remote")
modname = call.__module__.replace("cmdeploy.", "")
self._remote_cmdloop_channel.send((modname, call.__name__, kwargs))
while 1:
code, data = self._remote_cmdloop_channel.receive(timeout=self.timeout)
if log_callback is not None and code == "log":
log_callback(data)
elif code == "finish":
return data
elif code == "error":
raise self.FuncError(data)