mirror of
https://github.com/chatmail/relay.git
synced 2026-05-11 16:34:39 +00:00
Compare commits
9 Commits
link2xt/pt
...
migration-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c849036d0b | ||
|
|
bf371e7b6d | ||
|
|
35867153af | ||
|
|
610843a44a | ||
|
|
966754a346 | ||
|
|
87153667ed | ||
|
|
abe0cb5d08 | ||
|
|
8c8c37c822 | ||
|
|
e7bed4d2a1 |
@@ -1,6 +1,6 @@
|
|||||||
# Changelog for chatmail deployment
|
# Changelog for chatmail deployment
|
||||||
|
|
||||||
## untagged
|
## 1.8.0 2025-12-12
|
||||||
|
|
||||||
- Add imap_compress option to chatmail.ini
|
- Add imap_compress option to chatmail.ini
|
||||||
([#760](https://github.com/chatmail/relay/pull/760))
|
([#760](https://github.com/chatmail/relay/pull/760))
|
||||||
|
|||||||
7
CONTRIBUTING.md
Normal file
7
CONTRIBUTING.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Contributing to the chatmail relay
|
||||||
|
|
||||||
|
Commit messages follow the [Conventional Commits] notation.
|
||||||
|
We use [git-cliff] to generate the changelog from commit messages before the release.
|
||||||
|
|
||||||
|
[Conventional Commits]: https://www.conventionalcommits.org/
|
||||||
|
[git-cliff]: https://git-cliff.org/
|
||||||
15
RELEASE.md
Normal file
15
RELEASE.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Releasing a new version of chatmail relay
|
||||||
|
|
||||||
|
For example, to release version 1.9.0 of chatmail relay, do the following steps.
|
||||||
|
|
||||||
|
1. Update the changelog: `git cliff --unreleased --tag 1.9.0 --prepend CHANGELOG.md` or `git cliff -u -t 1.9.0 -p CHANGELOG.md`.
|
||||||
|
|
||||||
|
2. Open the changelog in the editor, edit it if required.
|
||||||
|
|
||||||
|
3. Commit the changes to the changelog with a commit message `chore(release): prepare for 1.9.0`.
|
||||||
|
|
||||||
|
3. Tag the release: `git tag --annotate 1.9.0`.
|
||||||
|
|
||||||
|
4. Push the release tag: `git push origin 1.9.0`.
|
||||||
|
|
||||||
|
5. Create a GitHub release: `gh release create 1.9.0`.
|
||||||
94
cliff.toml
Normal file
94
cliff.toml
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# git-cliff ~ configuration file
|
||||||
|
# https://git-cliff.org/docs/configuration
|
||||||
|
|
||||||
|
|
||||||
|
[changelog]
|
||||||
|
# A Tera template to be rendered for each release in the changelog.
|
||||||
|
# See https://keats.github.io/tera/docs/#introduction
|
||||||
|
body = """
|
||||||
|
{% if version %}\
|
||||||
|
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||||
|
{% else %}\
|
||||||
|
## [unreleased]
|
||||||
|
{% endif %}\
|
||||||
|
{% for group, commits in commits | group_by(attribute="group") %}
|
||||||
|
### {{ group | striptags | trim | upper_first }}
|
||||||
|
{% for commit in commits %}
|
||||||
|
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
|
||||||
|
{% if commit.breaking %}[**breaking**] {% endif %}\
|
||||||
|
{{ commit.message | upper_first }}\
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
"""
|
||||||
|
# Remove leading and trailing whitespaces from the changelog's body.
|
||||||
|
trim = true
|
||||||
|
# Render body even when there are no releases to process.
|
||||||
|
render_always = true
|
||||||
|
# An array of regex based postprocessors to modify the changelog.
|
||||||
|
postprocessors = [
|
||||||
|
# Replace the placeholder <REPO> with a URL.
|
||||||
|
#{ pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" },
|
||||||
|
]
|
||||||
|
# render body even when there are no releases to process
|
||||||
|
# render_always = true
|
||||||
|
# output file path
|
||||||
|
# output = "test.md"
|
||||||
|
|
||||||
|
[git]
|
||||||
|
# Parse commits according to the conventional commits specification.
|
||||||
|
# See https://www.conventionalcommits.org
|
||||||
|
conventional_commits = true
|
||||||
|
# Exclude commits that do not match the conventional commits specification.
|
||||||
|
filter_unconventional = true
|
||||||
|
# Require all commits to be conventional.
|
||||||
|
# Takes precedence over filter_unconventional.
|
||||||
|
require_conventional = false
|
||||||
|
# Split commits on newlines, treating each line as an individual commit.
|
||||||
|
split_commits = false
|
||||||
|
# An array of regex based parsers to modify commit messages prior to further processing.
|
||||||
|
commit_preprocessors = [
|
||||||
|
# Replace issue numbers with link templates to be updated in `changelog.postprocessors`.
|
||||||
|
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
|
||||||
|
# Check spelling of the commit message using https://github.com/crate-ci/typos.
|
||||||
|
# If the spelling is incorrect, it will be fixed automatically.
|
||||||
|
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
|
||||||
|
]
|
||||||
|
# Prevent commits that are breaking from being excluded by commit parsers.
|
||||||
|
protect_breaking_commits = false
|
||||||
|
# An array of regex based parsers for extracting data from the commit message.
|
||||||
|
# Assigns commits to groups.
|
||||||
|
# Optionally sets the commit's scope and can decide to exclude commits from further processing.
|
||||||
|
commit_parsers = [
|
||||||
|
{ message = "^feat", group = "Features" },
|
||||||
|
{ message = "^fix", group = "Bug Fixes" },
|
||||||
|
{ message = "^docs", group = "Documentation" },
|
||||||
|
{ message = "^perf", group = "Performance" },
|
||||||
|
{ message = "^refactor", group = "Refactor" },
|
||||||
|
{ message = "^style", group = "Styling" },
|
||||||
|
{ message = "^test", group = "Testing" },
|
||||||
|
{ message = "^chore\\(release\\): prepare for", skip = true },
|
||||||
|
{ message = "^chore\\(deps.*\\)", skip = true },
|
||||||
|
{ message = "^chore\\(pr\\)", skip = true },
|
||||||
|
{ message = "^chore\\(pull\\)", skip = true },
|
||||||
|
{ message = "^chore|^ci", group = "Miscellaneous Tasks" },
|
||||||
|
{ body = ".*security", group = "Security" },
|
||||||
|
{ message = "^revert", group = "Revert" },
|
||||||
|
{ message = ".*", group = "Other" },
|
||||||
|
]
|
||||||
|
# Exclude commits that are not matched by any commit parser.
|
||||||
|
filter_commits = false
|
||||||
|
# Fail on a commit that is not matched by any commit parser.
|
||||||
|
fail_on_unmatched_commit = false
|
||||||
|
# An array of link parsers for extracting external references, and turning them into URLs, using regex.
|
||||||
|
link_parsers = []
|
||||||
|
# Include only the tags that belong to the current branch.
|
||||||
|
use_branch_tags = false
|
||||||
|
# Order releases topologically instead of chronologically.
|
||||||
|
topo_order = false
|
||||||
|
# Order commits topologically instead of chronologically.
|
||||||
|
topo_order_commits = true
|
||||||
|
# Order of commits in each group/release within the changelog.
|
||||||
|
# Allowed values: newest, oldest
|
||||||
|
sort_commits = "oldest"
|
||||||
|
# Process submodules commits
|
||||||
|
recurse_submodules = false
|
||||||
@@ -13,6 +13,8 @@ from cmdeploy.basedeploy import (
|
|||||||
|
|
||||||
|
|
||||||
class DovecotDeployer(Deployer):
|
class DovecotDeployer(Deployer):
|
||||||
|
daemon_reload = False
|
||||||
|
|
||||||
def __init__(self, config, disable_mail):
|
def __init__(self, config, disable_mail):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.disable_mail = disable_mail
|
self.disable_mail = disable_mail
|
||||||
@@ -27,7 +29,7 @@ class DovecotDeployer(Deployer):
|
|||||||
|
|
||||||
def configure(self):
|
def configure(self):
|
||||||
configure_remote_units(self.config.mail_domain, self.units)
|
configure_remote_units(self.config.mail_domain, self.units)
|
||||||
self.need_restart = _configure_dovecot(self.config)
|
self.need_restart, self.daemon_reload = _configure_dovecot(self.config)
|
||||||
|
|
||||||
def activate(self):
|
def activate(self):
|
||||||
activate_remote_units(self.units)
|
activate_remote_units(self.units)
|
||||||
@@ -42,6 +44,7 @@ class DovecotDeployer(Deployer):
|
|||||||
running=False if self.disable_mail else True,
|
running=False if self.disable_mail else True,
|
||||||
enabled=False if self.disable_mail else True,
|
enabled=False if self.disable_mail else True,
|
||||||
restarted=restart,
|
restarted=restart,
|
||||||
|
daemon_reload=self.daemon_reload,
|
||||||
)
|
)
|
||||||
self.need_restart = False
|
self.need_restart = False
|
||||||
|
|
||||||
@@ -80,9 +83,10 @@ def _install_dovecot_package(package: str, arch: str):
|
|||||||
apt.deb(name=f"Install dovecot-{package}", src=deb_filename)
|
apt.deb(name=f"Install dovecot-{package}", src=deb_filename)
|
||||||
|
|
||||||
|
|
||||||
def _configure_dovecot(config: Config, debug: bool = False) -> bool:
|
def _configure_dovecot(config: Config, debug: bool = False) -> (bool, bool):
|
||||||
"""Configures Dovecot IMAP server."""
|
"""Configures Dovecot IMAP server."""
|
||||||
need_restart = False
|
need_restart = False
|
||||||
|
daemon_reload = False
|
||||||
|
|
||||||
main_config = files.template(
|
main_config = files.template(
|
||||||
src=get_resource("dovecot/dovecot.conf.j2"),
|
src=get_resource("dovecot/dovecot.conf.j2"),
|
||||||
@@ -134,4 +138,11 @@ def _configure_dovecot(config: Config, debug: bool = False) -> bool:
|
|||||||
)
|
)
|
||||||
need_restart |= timezone_env.changed
|
need_restart |= timezone_env.changed
|
||||||
|
|
||||||
return need_restart
|
restart_conf = files.put(
|
||||||
|
name="dovecot: restart automatically on failure",
|
||||||
|
src=get_resource("service/10_restart.conf"),
|
||||||
|
dest="/etc/systemd/system/dovecot.service.d/10_restart.conf",
|
||||||
|
)
|
||||||
|
daemon_reload |= restart_conf.changed
|
||||||
|
|
||||||
|
return need_restart, daemon_reload
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from cmdeploy.basedeploy import Deployer, get_resource
|
|||||||
|
|
||||||
class PostfixDeployer(Deployer):
|
class PostfixDeployer(Deployer):
|
||||||
required_users = [("postfix", None, ["opendkim"])]
|
required_users = [("postfix", None, ["opendkim"])]
|
||||||
|
daemon_reload = False
|
||||||
|
|
||||||
def __init__(self, config, disable_mail):
|
def __init__(self, config, disable_mail):
|
||||||
self.config = config
|
self.config = config
|
||||||
@@ -60,6 +61,13 @@ class PostfixDeployer(Deployer):
|
|||||||
mode="644",
|
mode="644",
|
||||||
)
|
)
|
||||||
need_restart |= login_map.changed
|
need_restart |= login_map.changed
|
||||||
|
|
||||||
|
restart_conf = files.put(
|
||||||
|
name="postfix: restart automatically on failure",
|
||||||
|
src=get_resource("service/10_restart.conf"),
|
||||||
|
dest="/etc/systemd/system/dovecot.service.d/10_restart.conf",
|
||||||
|
)
|
||||||
|
self.daemon_reload = restart_conf.changed
|
||||||
self.need_restart = need_restart
|
self.need_restart = need_restart
|
||||||
|
|
||||||
def activate(self):
|
def activate(self):
|
||||||
@@ -73,5 +81,6 @@ class PostfixDeployer(Deployer):
|
|||||||
running=False if self.disable_mail else True,
|
running=False if self.disable_mail else True,
|
||||||
enabled=False if self.disable_mail else True,
|
enabled=False if self.disable_mail else True,
|
||||||
restarted=restart,
|
restarted=restart,
|
||||||
|
daemon_reload=self.daemon_reload,
|
||||||
)
|
)
|
||||||
self.need_restart = False
|
self.need_restart = False
|
||||||
|
|||||||
3
cmdeploy/src/cmdeploy/service/10_restart.conf
Normal file
3
cmdeploy/src/cmdeploy/service/10_restart.conf
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
RestartSec=30
|
||||||
@@ -7,66 +7,88 @@ machine, you can use these steps. They were tested with a Linux laptop;
|
|||||||
you might need to adjust some of the steps to your environment.
|
you might need to adjust some of the steps to your environment.
|
||||||
|
|
||||||
Let’s assume that your ``mail_domain`` is ``mail.example.org``, all
|
Let’s assume that your ``mail_domain`` is ``mail.example.org``, all
|
||||||
involved machines run Debian 12, your old site’s IP address is
|
involved machines run Debian 12, your old site’s IP version 4 address is
|
||||||
``13.37.13.37``, and your new site’s IP address is ``13.12.23.42``.
|
``$OLD_IP4``, and your new site’s IP4 address is ``$NEW_IP4``.
|
||||||
|
|
||||||
Note, you should lower the TTLs of your DNS records to a value such as
|
First of all, you should lower the Time To Live (TTL) of your DNS records
|
||||||
300 (5 minutes) so the migration happens as smoothly as possible.
|
to a value such as 300 (5 minutes).
|
||||||
|
Short TTL values allow to change DNS records during the migration more timely.
|
||||||
|
|
||||||
During the guide you might get a warning about changed SSH Host keys; in
|
During the guide you might get a warning about changed SSH Host keys; in
|
||||||
this case, just run ``ssh-keygen -R "mail.example.org"`` as recommended.
|
this case, just run ``ssh-keygen -R "mail.example.org"`` as recommended.
|
||||||
|
|
||||||
1. First, disable mail services on the old site.
|
1. First, to make the downtime during the migration shorter,
|
||||||
|
let's transfer the current state of the mailboxes.
|
||||||
::
|
Login to your old machine (while forwarding your ssh-agent with ``ssh -A``)
|
||||||
|
|
||||||
cmdeploy run --disable-mail --ssh-host 13.37.13.37
|
|
||||||
|
|
||||||
Now your users will notice the migration and will not be able to send
|
|
||||||
or receive messages until the migration is completed.
|
|
||||||
|
|
||||||
2. Now we want to copy ``/home/vmail``, ``/var/lib/acme``,
|
|
||||||
``/etc/dkimkeys``, and ``/var/spool/postfix`` to
|
|
||||||
the new site. Login to the old site while forwarding your SSH agent
|
|
||||||
so you can copy directly from the old to the new site with your SSH
|
so you can copy directly from the old to the new site with your SSH
|
||||||
key:
|
key:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
ssh -A root@13.37.13.37
|
ssh -A root@$OLD_IP4
|
||||||
tar c - /home/vmail/mail /var/lib/acme /etc/dkimkeys /var/spool/postfix | ssh root@13.12.23.42 "tar x -C /"
|
tar c /home/vmail/mail | ssh root@$NEW_IP4 "tar x -C /"
|
||||||
|
|
||||||
This transfers all addresses, the TLS certificate,
|
This saves us time during the downtime,
|
||||||
|
at least the mailboxes are there already.
|
||||||
|
They contain user passwords, encrypted push notification tokens,
|
||||||
|
messages which might not have been fetched by all devices of the user yet,
|
||||||
|
and dovecot indexes which track the state of the mailbox.
|
||||||
|
|
||||||
|
2. Then, from your local machine, install chatmail on the new machine, but don't activate it yet:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
CMDEPLOY_STAGES=install,configure cmdeploy run --ssh-host $NEW_IP4
|
||||||
|
|
||||||
|
The services are disabled for now; we will enable them later.
|
||||||
|
We first need to make the new site fully operational.
|
||||||
|
|
||||||
|
3. Now it's getting serious: disable the mail services on the old site.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
cmdeploy run --disable-mail --ssh-host $OLD_IP4
|
||||||
|
|
||||||
|
Your users will start to notice the migration and will not be able to send
|
||||||
|
or receive messages until the migration is completed.
|
||||||
|
Other relays and mail servers will wait with delivering messages
|
||||||
|
until your relay is reachable again.
|
||||||
|
|
||||||
|
4. Now we want to copy ``/home/vmail``, ``/var/lib/acme``,
|
||||||
|
``/etc/dkimkeys``, and ``/var/spool/postfix`` to
|
||||||
|
the new site. Let's forward the SSH agent again to copy the files directly.
|
||||||
|
This time, we copy ``/home/vmail/mail`` with rsync to only copy the recent changes:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
ssh -A root@$OLD_IP4
|
||||||
|
tar c /var/lib/acme /etc/dkimkeys /var/spool/postfix | ssh root@$NEW_IP4 "tar x -C /"
|
||||||
|
rsync -azH /home/vmail/mail root@$NEW_IP4:/home/vmail/
|
||||||
|
|
||||||
|
This transfers all messages which have not been fetched yet, the TLS certificate,
|
||||||
and DKIM keys (so DKIM DNS record remains valid).
|
and DKIM keys (so DKIM DNS record remains valid).
|
||||||
It also preserves the Postfix mail spool so any messages
|
It also preserves the Postfix mail spool so any messages
|
||||||
pending delivery will still be delivered.
|
pending delivery will still be delivered.
|
||||||
|
|
||||||
3. Install chatmail on the new machine:
|
5. Now login to the new site and run the following to ensure the ownership is correct
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
cmdeploy run --disable-mail --ssh-host 13.12.23.42
|
|
||||||
|
|
||||||
Postfix and Dovecot are disabled for now; we will enable them later.
|
|
||||||
We first need to make the new site fully operational.
|
|
||||||
|
|
||||||
4. On the new site, run the following to ensure the ownership is correct
|
|
||||||
in case UIDs/GIDs changed:
|
in case UIDs/GIDs changed:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
ssh root@$NEW_IP4
|
||||||
chown root: -R /var/lib/acme
|
chown root: -R /var/lib/acme
|
||||||
chown opendkim: -R /etc/dkimkeys
|
chown opendkim: -R /etc/dkimkeys
|
||||||
chown vmail: -R /home/vmail/mail
|
chown vmail: -R /home/vmail/mail
|
||||||
|
|
||||||
5. Now, update DNS entries.
|
6. Now, update the DNS entries.
|
||||||
|
You only need to change the ``A`` and ``AAAA`` records, for example:
|
||||||
|
|
||||||
If other MTAs try to deliver messages to your chatmail domain they
|
::
|
||||||
may fail intermittently, as DNS catches up with the new site settings
|
|
||||||
but normally will retry delivering messages for at least a week, so
|
|
||||||
messages will not be lost.
|
|
||||||
|
|
||||||
6. Finally, you can execute ``cmdeploy run --ssh-host 13.12.23.42`` to
|
mail.example.org. IN A $NEW_IP4
|
||||||
|
mail.example.org. IN AAAA $NEW_IP6
|
||||||
|
|
||||||
|
7. Finally, you can execute ``CMDEPLOY_STAGES=activate cmdeploy run --ssh-host $NEW_IP4`` to
|
||||||
turn on chatmail on the new relay. Your users will be able to use the
|
turn on chatmail on the new relay. Your users will be able to use the
|
||||||
chatmail relay as soon as the DNS changes have propagated. Voilà!
|
chatmail relay as soon as the DNS changes have propagated. Voilà!
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user