mirror of
https://github.com/chatmail/relay.git
synced 2026-05-10 16:04:37 +00:00
Compare commits
42 Commits
j-g00da/fi
...
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:
|
||||
|
||||
```mermaid
|
||||
@@ -48,3 +50,66 @@ graph LR;
|
||||
The edges in this graph should not be taken too literally; they
|
||||
reflect some sort of communication path or dependency relationship
|
||||
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
|
||||
|
||||
- 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
|
||||
([#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 ..deployer import Deployer
|
||||
|
||||
def deploy_acmetool(email="", domains=[]):
|
||||
"""Deploy acmetool."""
|
||||
apt.packages(
|
||||
name="Install acmetool",
|
||||
packages=["acmetool"],
|
||||
)
|
||||
|
||||
files.put(
|
||||
src=importlib.resources.files(__package__).joinpath("acmetool.cron").open("rb"),
|
||||
dest="/etc/cron.d/acmetool",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
)
|
||||
class AcmetoolDeployer(Deployer):
|
||||
def __init__(self, *, email, domains, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.domains = domains
|
||||
self.email = email
|
||||
self.need_restart = False
|
||||
|
||||
files.put(
|
||||
src=importlib.resources.files(__package__).joinpath("acmetool.hook").open("rb"),
|
||||
dest="/usr/lib/acme/hooks/nginx",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="744",
|
||||
)
|
||||
@staticmethod
|
||||
def install_impl():
|
||||
apt.packages(
|
||||
name="Install acmetool",
|
||||
packages=["acmetool"],
|
||||
)
|
||||
|
||||
files.template(
|
||||
src=importlib.resources.files(__package__).joinpath("response-file.yaml.j2"),
|
||||
dest="/var/lib/acme/conf/responses",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
email=email,
|
||||
)
|
||||
def configure_impl(self):
|
||||
files.put(
|
||||
src=importlib.resources.files(__package__).joinpath("acmetool.cron").open("rb"),
|
||||
dest="/etc/cron.d/acmetool",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
)
|
||||
|
||||
files.template(
|
||||
src=importlib.resources.files(__package__).joinpath("target.yaml.j2"),
|
||||
dest="/var/lib/acme/conf/target",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
)
|
||||
files.put(
|
||||
src=importlib.resources.files(__package__).joinpath("acmetool.hook").open("rb"),
|
||||
dest="/usr/lib/acme/hooks/nginx",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="744",
|
||||
)
|
||||
|
||||
service_file = files.put(
|
||||
src=importlib.resources.files(__package__).joinpath(
|
||||
"acmetool-redirector.service"
|
||||
),
|
||||
dest="/etc/systemd/system/acmetool-redirector.service",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
)
|
||||
files.template(
|
||||
src=importlib.resources.files(__package__).joinpath("response-file.yaml.j2"),
|
||||
dest="/var/lib/acme/conf/responses",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
email=self.email,
|
||||
)
|
||||
|
||||
systemd.service(
|
||||
name="Setup acmetool-redirector service",
|
||||
service="acmetool-redirector.service",
|
||||
running=True,
|
||||
enabled=True,
|
||||
restarted=service_file.changed,
|
||||
)
|
||||
files.template(
|
||||
src=importlib.resources.files(__package__).joinpath("target.yaml.j2"),
|
||||
dest="/var/lib/acme/conf/target",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
)
|
||||
|
||||
server.shell(
|
||||
name=f"Request certificate for: {', '.join(domains)}",
|
||||
commands=[f"acmetool want --xlog.severity=debug {' '.join(domains)}"],
|
||||
)
|
||||
service_file = files.put(
|
||||
src=importlib.resources.files(__package__).joinpath(
|
||||
"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