mirror of
https://github.com/chatmail/relay.git
synced 2026-05-14 01:44:38 +00:00
Compare commits
42 Commits
fix/multip
...
hpk/cliff-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a77f5c5f42 | ||
|
|
84b970f1ea | ||
|
|
1f4ba7bf43 | ||
|
|
5de5ba46ad | ||
|
|
337e045284 | ||
|
|
b60bc1862a | ||
|
|
579e7c767d | ||
|
|
a735543c80 | ||
|
|
948efff70d | ||
|
|
9945402d4c | ||
|
|
b39c27c5cc | ||
|
|
38ba28be8a | ||
|
|
1aa4896260 | ||
|
|
823bf88c74 | ||
|
|
78b326dbd1 | ||
|
|
e708027edb | ||
|
|
27b72039c8 | ||
|
|
32534251bf | ||
|
|
1a03d56c80 | ||
|
|
e043c1baff | ||
|
|
a9d3709663 | ||
|
|
741948682f | ||
|
|
68b78bf6d2 | ||
|
|
0e756c653d | ||
|
|
69de709b0f | ||
|
|
d11163629b | ||
|
|
e92c8766f6 | ||
|
|
334e468889 | ||
|
|
33e6807ca4 | ||
|
|
2029acc5a9 | ||
|
|
d1788b7c65 | ||
|
|
84ab4bb6b8 | ||
|
|
04451ad537 | ||
|
|
d09a118e54 | ||
|
|
5b982a3b0f | ||
|
|
71636b8250 | ||
|
|
e7df1a43a3 | ||
|
|
cfc94a37b3 | ||
|
|
22b77168ed | ||
|
|
ffcd657a88 | ||
|
|
39fd04473c | ||
|
|
49613f7e71 |
@@ -1,4 +1,6 @@
|
|||||||
This diagram shows components of the chatmail server; this is a draft
|
## Chatmail Relay
|
||||||
|
|
||||||
|
This diagram shows components of the chatmail relay; this is a draft
|
||||||
overview as of mid-August 2025:
|
overview as of mid-August 2025:
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
@@ -48,3 +50,66 @@ graph LR;
|
|||||||
The edges in this graph should not be taken too literally; they
|
The edges in this graph should not be taken too literally; they
|
||||||
reflect some sort of communication path or dependency relationship
|
reflect some sort of communication path or dependency relationship
|
||||||
between components of the chatmail server.
|
between components of the chatmail server.
|
||||||
|
|
||||||
|
## cmdeploy
|
||||||
|
|
||||||
|
cmdeploy is a Python program that uses the pyinfra library to deploy
|
||||||
|
chatmail servers, with all the necessary software, configuration, and
|
||||||
|
services. The deployment process performs three primary types of operation:
|
||||||
|
|
||||||
|
1. Installation of software, universal across all deployments.
|
||||||
|
2. Configuration of software, with deploy-specific variations.
|
||||||
|
3. Activation of services.
|
||||||
|
|
||||||
|
The process is implemented through a family of "deployer" objects
|
||||||
|
which all derive from a common `Deployer` base class, defined in
|
||||||
|
[deployer.py](cmdeploy/src/cmdeploy/deployer.py). Each object
|
||||||
|
provides implementation methods for the three stages -- install,
|
||||||
|
configure, and activate. The top-level procedure in
|
||||||
|
`deploy_chatmail()` calls these methods for all the deployer objects,
|
||||||
|
first calling all the install methods, then the configure methods,
|
||||||
|
then the activate methods.
|
||||||
|
|
||||||
|
The base class also implements support for a CMDEPLOY_STAGES
|
||||||
|
environment variable, which allows limiting the process to specific
|
||||||
|
stages. Note that some deployers are stateful between the stages
|
||||||
|
(this is one reason why they are implemented as objects), and that
|
||||||
|
state will not get propagated between stages when run in separate
|
||||||
|
invocations of cmdeploy. This environment variable is intended for
|
||||||
|
use in future revisions to support building Docker images with
|
||||||
|
software pre-installed, and configuration of containers at run time
|
||||||
|
from environmnet variables.
|
||||||
|
|
||||||
|
The `install_impl()` method for the deployer classes is static, to
|
||||||
|
ensure that it does not rely on any object state, in particular, the
|
||||||
|
configuration details of the deployment. This helps ensure that all
|
||||||
|
install methods are suitable for running as part of a container image
|
||||||
|
build.
|
||||||
|
|
||||||
|
Operations that start services for systemd-based deployments should
|
||||||
|
only be called from the `activate_impl()` methods. These methods will
|
||||||
|
not be called in non-systemd container environments.
|
||||||
|
|
||||||
|
### Deployer objects
|
||||||
|
|
||||||
|
One might ask why the deployers are implemented as object classes, as
|
||||||
|
opposed to callable functions or the like. There are various reasons
|
||||||
|
why objects are a good fit for the deployment process.
|
||||||
|
|
||||||
|
1. Objects provide a way to organize the install, configure, and
|
||||||
|
deploy operations for each component that is installed, supporting a
|
||||||
|
"driver" type of pattern. This could be implemented in other ways
|
||||||
|
without objects, such as function jump tables, but objects provide a
|
||||||
|
clean and formalized way to do essentially the same thing.
|
||||||
|
|
||||||
|
2. Class inheritance provides a natural way to define
|
||||||
|
component-specific operations for the various stages of deployment, by
|
||||||
|
overriding the no-op stub methods in the base class. The base class
|
||||||
|
handles policy decisions about which stages are to be executed,
|
||||||
|
ensuring consistent handling of the stages in a central location.
|
||||||
|
|
||||||
|
3. Some of the components track state between stages, basing decisions
|
||||||
|
like whether to restart a service on whether the software or
|
||||||
|
configuration of that service was changed in an earlier stage.
|
||||||
|
Keeping track of state between method calls is an ideal use case for
|
||||||
|
objects.
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
## untagged
|
## untagged
|
||||||
|
|
||||||
|
- Organized cmdeploy into install, configure, and activate stages
|
||||||
|
([#695](https://github.com/chatmail/relay/pull/695))
|
||||||
|
|
||||||
- don't deploy the website if there are merge conflicts in the www folder
|
- don't deploy the website if there are merge conflicts in the www folder
|
||||||
([#714](https://github.com/chatmail/relay/pull/714))
|
([#714](https://github.com/chatmail/relay/pull/714))
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,66 +2,79 @@ import importlib.resources
|
|||||||
|
|
||||||
from pyinfra.operations import apt, files, server, systemd
|
from pyinfra.operations import apt, files, server, systemd
|
||||||
|
|
||||||
|
from ..deployer import Deployer
|
||||||
|
|
||||||
def deploy_acmetool(email="", domains=[]):
|
|
||||||
"""Deploy acmetool."""
|
|
||||||
apt.packages(
|
|
||||||
name="Install acmetool",
|
|
||||||
packages=["acmetool"],
|
|
||||||
)
|
|
||||||
|
|
||||||
files.put(
|
class AcmetoolDeployer(Deployer):
|
||||||
src=importlib.resources.files(__package__).joinpath("acmetool.cron").open("rb"),
|
def __init__(self, *, email, domains, **kwargs):
|
||||||
dest="/etc/cron.d/acmetool",
|
super().__init__(**kwargs)
|
||||||
user="root",
|
self.domains = domains
|
||||||
group="root",
|
self.email = email
|
||||||
mode="644",
|
self.need_restart = False
|
||||||
)
|
|
||||||
|
|
||||||
files.put(
|
@staticmethod
|
||||||
src=importlib.resources.files(__package__).joinpath("acmetool.hook").open("rb"),
|
def install_impl():
|
||||||
dest="/usr/lib/acme/hooks/nginx",
|
apt.packages(
|
||||||
user="root",
|
name="Install acmetool",
|
||||||
group="root",
|
packages=["acmetool"],
|
||||||
mode="744",
|
)
|
||||||
)
|
|
||||||
|
|
||||||
files.template(
|
def configure_impl(self):
|
||||||
src=importlib.resources.files(__package__).joinpath("response-file.yaml.j2"),
|
files.put(
|
||||||
dest="/var/lib/acme/conf/responses",
|
src=importlib.resources.files(__package__).joinpath("acmetool.cron").open("rb"),
|
||||||
user="root",
|
dest="/etc/cron.d/acmetool",
|
||||||
group="root",
|
user="root",
|
||||||
mode="644",
|
group="root",
|
||||||
email=email,
|
mode="644",
|
||||||
)
|
)
|
||||||
|
|
||||||
files.template(
|
files.put(
|
||||||
src=importlib.resources.files(__package__).joinpath("target.yaml.j2"),
|
src=importlib.resources.files(__package__).joinpath("acmetool.hook").open("rb"),
|
||||||
dest="/var/lib/acme/conf/target",
|
dest="/usr/lib/acme/hooks/nginx",
|
||||||
user="root",
|
user="root",
|
||||||
group="root",
|
group="root",
|
||||||
mode="644",
|
mode="744",
|
||||||
)
|
)
|
||||||
|
|
||||||
service_file = files.put(
|
files.template(
|
||||||
src=importlib.resources.files(__package__).joinpath(
|
src=importlib.resources.files(__package__).joinpath("response-file.yaml.j2"),
|
||||||
"acmetool-redirector.service"
|
dest="/var/lib/acme/conf/responses",
|
||||||
),
|
user="root",
|
||||||
dest="/etc/systemd/system/acmetool-redirector.service",
|
group="root",
|
||||||
user="root",
|
mode="644",
|
||||||
group="root",
|
email=self.email,
|
||||||
mode="644",
|
)
|
||||||
)
|
|
||||||
|
|
||||||
systemd.service(
|
files.template(
|
||||||
name="Setup acmetool-redirector service",
|
src=importlib.resources.files(__package__).joinpath("target.yaml.j2"),
|
||||||
service="acmetool-redirector.service",
|
dest="/var/lib/acme/conf/target",
|
||||||
running=True,
|
user="root",
|
||||||
enabled=True,
|
group="root",
|
||||||
restarted=service_file.changed,
|
mode="644",
|
||||||
)
|
)
|
||||||
|
|
||||||
server.shell(
|
service_file = files.put(
|
||||||
name=f"Request certificate for: {', '.join(domains)}",
|
src=importlib.resources.files(__package__).joinpath(
|
||||||
commands=[f"acmetool want --xlog.severity=debug {' '.join(domains)}"],
|
"acmetool-redirector.service"
|
||||||
)
|
),
|
||||||
|
dest="/etc/systemd/system/acmetool-redirector.service",
|
||||||
|
user="root",
|
||||||
|
group="root",
|
||||||
|
mode="644",
|
||||||
|
)
|
||||||
|
self.need_restart = service_file.changed
|
||||||
|
|
||||||
|
def activate_impl(self):
|
||||||
|
systemd.service(
|
||||||
|
name="Setup acmetool-redirector service",
|
||||||
|
service="acmetool-redirector.service",
|
||||||
|
running=True,
|
||||||
|
enabled=True,
|
||||||
|
restarted=self.need_restart,
|
||||||
|
)
|
||||||
|
self.need_restart = False
|
||||||
|
|
||||||
|
server.shell(
|
||||||
|
name=f"Request certificate for: {', '.join(self.domains)}",
|
||||||
|
commands=[f"acmetool want --xlog.severity=debug {' '.join(self.domains)}"],
|
||||||
|
)
|
||||||
|
|||||||
59
cmdeploy/src/cmdeploy/deployer.py
Normal file
59
cmdeploy/src/cmdeploy/deployer.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from pyinfra.operations import server
|
||||||
|
|
||||||
|
|
||||||
|
class Deployment:
|
||||||
|
def install(self, deployer):
|
||||||
|
# optional 'required_users' contains a list of (user, group, secondary-group-list) tuples.
|
||||||
|
# If the group is None, no group is created corresponding to that user.
|
||||||
|
# If the secondary group list is not None, all listed groups are created as well.
|
||||||
|
required_users = getattr(deployer, "required_users", [])
|
||||||
|
for user, group, groups in required_users:
|
||||||
|
if group is not None:
|
||||||
|
server.group(
|
||||||
|
name="Create {} group".format(group), group=group, system=True
|
||||||
|
)
|
||||||
|
if groups is not None:
|
||||||
|
for group2 in groups:
|
||||||
|
server.group(
|
||||||
|
name="Create {} group".format(group2), group=group2, system=True
|
||||||
|
)
|
||||||
|
server.user(
|
||||||
|
name="Create {} user".format(user),
|
||||||
|
user=user,
|
||||||
|
group=group,
|
||||||
|
groups=groups,
|
||||||
|
system=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
ret = bool(deployer.install())
|
||||||
|
if ret:
|
||||||
|
deployer.need_restart = True
|
||||||
|
|
||||||
|
def configure(self, deployer):
|
||||||
|
deployer.configure()
|
||||||
|
|
||||||
|
def activate(self, deployer):
|
||||||
|
deployer.activate()
|
||||||
|
|
||||||
|
def perform_stages(self, deployers):
|
||||||
|
default_stages = "install,configure,activate"
|
||||||
|
stages = os.getenv("CMDEPLOY_STAGES", default_stages).split(",")
|
||||||
|
|
||||||
|
for stage in stages:
|
||||||
|
for deployer in deployers:
|
||||||
|
getattr(self, stage)(deployer)
|
||||||
|
|
||||||
|
|
||||||
|
class Deployer:
|
||||||
|
need_restart = False
|
||||||
|
|
||||||
|
def install(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def configure(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def activate(self):
|
||||||
|
pass
|
||||||
Binary file not shown.
3
cmdeploy/src/cmdeploy/policy-rc.d
Executable file
3
cmdeploy/src/cmdeploy/policy-rc.d
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
echo "All runlevel operations denied by policy" >&2
|
||||||
|
exit 101
|
||||||
Reference in New Issue
Block a user