From 792225e2562cfcbc149db1a23e95292a1ca2bc02 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Thu, 22 May 2025 02:45:55 +0200 Subject: [PATCH] Introduce stateVersion concept With upcoming changes to the dovecot home and maildirectories we need to introduce a way to nudge users to inform themselves about manual migration steps they might need to carry out. The idea here is to allow us to safely make breaking changes and notify the user of required migration steps at eval time, so they can make the necessary changes in time. --- default.nix | 16 +++++++++++++++ docs/howto-develop.rst | 41 ++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + docs/migrations.rst | 22 ++++++++++++++++++++ docs/setup-guide.rst | 1 + mail-server/assertions.nix | 7 ++++++- tests/lib/config.nix | 6 +++++- tests/minimal.nix | 5 ++++- tests/multiple.nix | 5 ++++- 9 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 docs/migrations.rst diff --git a/default.nix b/default.nix index aaa0987..71effa0 100644 --- a/default.nix +++ b/default.nix @@ -25,6 +25,22 @@ in options.mailserver = { enable = mkEnableOption "nixos-mailserver"; + stateVersion = mkOption { + type = types.nullOr types.ints.positive; + default = null; + description = '' + Tracking stateful version changes as an incrementing number. + + When a new release comes out we may require manual migration steps to + be completed, before the new version can be put into production. + + If your `stateVersion` is too low one or multiple assertions may + trigger to give you instructions on what migrations steps are required + to continue. Increase the `stateVersion` as instructed by the assertion + message. + ''; + }; + openFirewall = mkOption { type = types.bool; default = true; diff --git a/docs/howto-develop.rst b/docs/howto-develop.rst index 40527f9..4261418 100644 --- a/docs/howto-develop.rst +++ b/docs/howto-develop.rst @@ -64,3 +64,44 @@ To build the documentation, you need to enable `Nix Flakes $ nix build .#documentation $ xdg-open result/index.html + + +Manual migrations +----------------- + +We need to take great care around providing a migration story around breaking +changes. If manual intervention becomes necessary we provide the `stateVersion` +option to notify the user that they need to complete a migration before +they can deploy an update. + +If that is the case for your change, find the highest `stateVersion` that is +being asserted on in `mail-server/assertions.nix`. Then pick the next number +and add a new assertion, write a good summary describing the issue and what +remediation steps are necessary. Finally reference the URL to the specific +section on the migration page in the documentation. + +.. code-block:: nix + + { + assertions = [ + { + assertion = config.mailserver.stateVersion < 1; + message = '' + Problem: The home directory for the foobar service is snafu. + Remediation: + - Stop the `foobar.service` + - Rename `/var/lib/foobaz` to `/var/lib/foobar` + - Increase the `mailserver.stateVersion` to 1. + + Check https://nixos-mailserver.readthedocs.io/en/latest/migrations.html#specific-anchor-here for further details. + ''; + } + ]; + } + +The setup guide should always reference the latest `stateVersion`, since we +don't require any migration steps for new setups. + +The migration documentation should paint a more complete picture about the steps +that need to be carried out and why this has become necessary. Make sure to +reference the correct anchor in the URL you put into the assertion message. diff --git a/docs/index.rst b/docs/index.rst index 2fd1e1a..d31ed27 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -18,6 +18,7 @@ Welcome to NixOS Mailserver's documentation! faq release-notes options + migrations .. toctree:: :maxdepth: 1 diff --git a/docs/migrations.rst b/docs/migrations.rst new file mode 100644 index 0000000..bd52196 --- /dev/null +++ b/docs/migrations.rst @@ -0,0 +1,22 @@ +Migrations +========== + +With mail server configuration best practices changing over time we might need +to make changes that require you to complete manual migration steps before you +can deploy a new version of NixOS mailserver. + +The initial `mailserver.stateVersion` value should be copied from the setup +guide that you used to initially set up your mail server. If in doubt you can +always initialize it at `1` and walk through all assertions, that might apply +to your setup. + +NixOS 25.11 +----------- + +This option was introduced in the NixOS 25.11 release cycle, in which case you +can safely initialize its value at `1`. + +:: code-block: nix + + mailserver.stateVersion = 1; + diff --git a/docs/setup-guide.rst b/docs/setup-guide.rst index 5f6f903..f92ef20 100644 --- a/docs/setup-guide.rst +++ b/docs/setup-guide.rst @@ -72,6 +72,7 @@ common ones. mailserver = { enable = true; + stateVersion = 1; fqdn = "mail.example.com"; domains = [ "example.com" ]; diff --git a/mail-server/assertions.nix b/mail-server/assertions.nix index 91921c6..b30ccaa 100644 --- a/mail-server/assertions.nix +++ b/mail-server/assertions.nix @@ -1,6 +1,11 @@ { config, lib, ... }: { - assertions = lib.optionals config.mailserver.ldap.enable [ + assertions = lib.optionals config.mailserver.enable [ + { + assertion = config.mailserver.stateVersion != null; + message = "The `mailserver.stateVersion` option is not set. Check https://nixos-mailserver.readthedocs.io/en/latest/migrations.html to determine the proper value to initialize it at."; + } + ] ++ lib.optionals config.mailserver.ldap.enable [ { assertion = config.mailserver.loginAccounts == {}; message = "When the LDAP support is enable (mailserver.ldap.enable = true), it is not possible to define mailserver.loginAccounts"; diff --git a/tests/lib/config.nix b/tests/lib/config.nix index 68a1b2e..09ae517 100644 --- a/tests/lib/config.nix +++ b/tests/lib/config.nix @@ -1,3 +1,7 @@ { - security.dhparams.defaultBitSize = 2048; # minimum size required by dovecot + # Testing eval failures that result from stateVersion assertion is out of scope + mailserver.stateVersion = 999; + + # minimum size required by dovecot + security.dhparams.defaultBitSize = 2048; } diff --git a/tests/minimal.nix b/tests/minimal.nix index 407f221..e78814e 100644 --- a/tests/minimal.nix +++ b/tests/minimal.nix @@ -18,7 +18,10 @@ name = "minimal"; nodes.machine = { - imports = [ ./../default.nix ]; + imports = [ + ../default.nix + ./lib/config.nix + ]; }; testScript = '' diff --git a/tests/multiple.nix b/tests/multiple.nix index 2427feb..3e71cd6 100644 --- a/tests/multiple.nix +++ b/tests/multiple.nix @@ -16,7 +16,10 @@ let password = pkgs.writeText "password" "password"; domainGenerator = domain: { pkgs, ... }: { - imports = [../default.nix]; + imports = [ + ../default.nix + ./lib/config.nix + ]; environment.systemPackages = with pkgs; [ netcat ]; virtualisation.memorySize = 1024; mailserver = {