From 4fcab839d7fe55cb6544ac57e615dd3c09cfeeb7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Na=C3=AFm=20Favier?= <n@monade.li>
Date: Wed, 30 Nov 2022 22:30:45 +0100
Subject: [PATCH] docs: use MarkDown for option docs

---
 .readthedocs.yaml               |    4 +-
 default.nix                     |   51 +-
 docs/add-roundcube.rst          |    2 +-
 docs/conf.py                    |   12 +-
 docs/howto-develop.rst          |    8 +-
 docs/options.md                 | 1202 ++++++++++++++++++++++++++++
 docs/options.rst                | 1319 -------------------------------
 docs/requirements.txt           |    6 +-
 flake.lock                      |   17 +
 flake.nix                       |   90 +--
 scripts/generate-options.py     |   81 ++
 scripts/generate-rst-options.py |   87 --
 shell.nix                       |   11 +-
 13 files changed, 1403 insertions(+), 1487 deletions(-)
 create mode 100644 docs/options.md
 delete mode 100644 docs/options.rst
 create mode 100644 scripts/generate-options.py
 delete mode 100644 scripts/generate-rst-options.py

diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 3918d82..eb6988e 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -5,9 +5,9 @@
 version: 2
 
 build:
-  os: ubuntu-20.04
+  os: ubuntu-22.04
   tools:
-    python: "3.9"
+    python: "3"
 
 sphinx:
    configuration: docs/conf.py
diff --git a/default.nix b/default.nix
index 275ac83..ef27f43 100644
--- a/default.nix
+++ b/default.nix
@@ -79,7 +79,7 @@ in
               ```
 
               Warning: this is stored in plaintext in the Nix store!
-              Use `hashedPasswordFile` instead.
+              Use {option}`mailserver.loginAccounts.<name>.hashedPasswordFile` instead.
             '';
           };
 
@@ -156,7 +156,7 @@ in
             description = ''
               Specifies if the account should be a send-only account.
               Emails sent to send-only accounts will be rejected from
-              unauthorized senders with the sendOnlyRejectMessage
+              unauthorized senders with the `sendOnlyRejectMessage`
               stating the reason.
             '';
           };
@@ -200,7 +200,7 @@ in
       description = ''
         Folder to store search indices. If null, indices are stored
         along with email, which could not necessarily be desirable,
-        especially when the fullTextSearch option is enable since
+        especially when {option}`mailserver.fullTextSearch.enable` is `true` since
         indices it creates are voluminous and do not need to be backed
         up.
 
@@ -242,8 +242,8 @@ in
         default = "no";
         description = ''
           Fail searches when no index is available. If set to
-          <literal>body</literal>, then only body searches (as opposed to
-          header) are affected. If set to <literal>no</literal>, searches may
+          `body`, then only body searches (as opposed to
+          header) are affected. If set to `no`, searches may
           fall back to a very slow brute force search.
         '';
       };
@@ -281,7 +281,7 @@ in
         randomizedDelaySec = mkOption {
           type = types.int;
           default = 1000;
-          description = "Run the maintenance job not exactly at the time specified with <literal>onCalendar</literal>, but plus or minus this many seconds.";
+          description = "Run the maintenance job not exactly at the time specified with `onCalendar`, but plus or minus this many seconds.";
         };
       };
     };
@@ -333,7 +333,7 @@ in
         the value {`"user@example.com" = "user@elsewhere.com";}`
         means that mails to `user@example.com` are forwarded to
         `user@elsewhere.com`. The difference with the
-        `extraVirtualAliases` option is that `user@elsewhere.com`
+        {option}`mailserver.extraVirtualAliases` option is that `user@elsewhere.com`
         can't send mail as `user@example.com`. Also, this option
         allows to forward mails to external addresses.
       '';
@@ -367,7 +367,7 @@ in
       description = ''
         The unix UID of the virtual mail user.  Be mindful that if this is
         changed, you will need to manually adjust the permissions of
-        mailDirectory.
+        `mailDirectory`.
       '';
     };
 
@@ -582,7 +582,7 @@ in
       type = types.str;
       default = "mail";
       description = ''
-
+        The DKIM selector.
       '';
     };
 
@@ -590,7 +590,7 @@ in
       type = types.path;
       default = "/var/dkim";
       description = ''
-
+        The DKIM directory.
       '';
     };
 
@@ -601,7 +601,7 @@ in
             How many bits in generated DKIM keys. RFC6376 advises minimum 1024-bit keys.
 
             If you have already deployed a key with a different number of bits than specified
-            here, then you should use a different selector (dkimSelector). In order to get
+            here, then you should use a different selector ({option}`mailserver.dkimSelector`). In order to get
             this package to generate a key with the new number of bits, you will either have to
             change the selector or delete the old key file.
         '';
@@ -673,7 +673,7 @@ in
         type = types.str;
         example = "ACME Corp.";
         description = ''
-          The name of your organization used in the <literal>org_name</literal> attribute in
+          The name of your organization used in the `org_name` attribute in
           DMARC reports.
         '';
       };
@@ -681,7 +681,7 @@ in
       fromName = mkOption {
         type = types.str;
         default = cfg.dmarcReporting.organizationName;
-        defaultText = literalExpression "organizationName";
+        defaultText = literalMD "{option}`mailserver.dmarcReporting.organizationName`";
         description = ''
           The sender name for DMARC reports. Defaults to the organization name.
         '';
@@ -738,7 +738,7 @@ in
         if (ip == "0.0.0.0" || ip == "::")
         then "127.0.0.1"
         else if isIpv6 ip then "[${ip}]" else ip;
-        defaultText = lib.literalDocBook "computed from <option>config.services.redis.servers.rspamd.bind</option>";
+        defaultText = lib.literalMD "computed from `config.services.redis.servers.rspamd.bind`";
         description = ''
           Address that rspamd should use to contact redis.
         '';
@@ -776,7 +776,7 @@ in
     sendingFqdn = mkOption {
       type = types.str;
       default = cfg.fqdn;
-      defaultText = "config.mailserver.fqdn";
+      defaultText = lib.literalMD "{option}`mailserver.fqdn`";
       example = "myserver.example.com";
       description = ''
         The fully qualified domain name of the mail server used to
@@ -792,7 +792,7 @@ in
 
         This setting allows the server to identify as
         myserver.example.com when forwarding mail, independently of
-        `fqdn` (which, for SSL reasons, should generally be the name
+        {option}`mailserver.fqdn` (which, for SSL reasons, should generally be the name
         to which the user connects).
 
         Set this to the name to which the sending IP's reverse DNS
@@ -864,7 +864,7 @@ in
                 start program = "${pkgs.systemd}/bin/systemctl start rspamd"
                 stop program = "${pkgs.systemd}/bin/systemctl stop rspamd"
         '';
-        defaultText = lib.literalDocBook "see source";
+        defaultText = lib.literalMD "see [source](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/blob/master/default.nix)";
         description = ''
           The configuration used for monitoring via monit.
           Use a mail address that you actively check and set it via 'set alert ...'.
@@ -881,7 +881,8 @@ in
         description = ''
           The location where borg saves the backups.
           This can be a local path or a remote location such as user@host:/path/to/repo.
-          It is exported and thus available as an environment variable to cmdPreexec and cmdPostexec.
+          It is exported and thus available as an environment variable to
+          {option}`mailserver.borgbackup.cmdPreexec` and {option}`mailserver.borgbackup.cmdPostexec`.
         '';
       };
 
@@ -941,7 +942,7 @@ in
           default = "none";
           description = ''
             The backup can be encrypted by choosing any other value than 'none'.
-            When using encryption the password / passphrase must be provided in passphraseFile.
+            When using encryption the password/passphrase must be provided in `passphraseFile`.
           '';
         };
 
@@ -964,6 +965,7 @@ in
       locations = mkOption {
         type = types.listOf types.path;
         default = [cfg.mailDirectory];
+        defaultText = lib.literalExpression "[ config.mailserver.mailDirectory ]";
         description = "The locations that are to be backed up by borg.";
       };
 
@@ -984,9 +986,10 @@ in
         default = null;
         description = ''
           The command to be executed before each backup operation.
-          This is called prior to borg init in the same script that runs borg init and create and cmdPostexec.
-          Example:
-            export BORG_RSH="ssh -i /path/to/private/key"
+          This is called prior to borg init in the same script that runs borg init and create and `cmdPostexec`.
+        '';
+        example = ''
+          export BORG_RSH="ssh -i /path/to/private/key"
         '';
       };
 
@@ -996,7 +999,7 @@ in
         description = ''
           The command to be executed after each backup operation.
           This is called after borg create completed successfully and in the same script that runs
-          cmdPreexec, borg init and create.
+          `cmdPreexec`, borg init and create.
         '';
       };
 
@@ -1009,7 +1012,7 @@ in
         example = true;
         description = ''
           Whether to enable automatic reboot after kernel upgrades.
-          This is to be used in conjunction with system.autoUpgrade.enable = true"
+          This is to be used in conjunction with `system.autoUpgrade.enable = true;`
         '';
       };
       method = mkOption {
diff --git a/docs/add-roundcube.rst b/docs/add-roundcube.rst
index 6d4795c..4e6be83 100644
--- a/docs/add-roundcube.rst
+++ b/docs/add-roundcube.rst
@@ -1,5 +1,5 @@
 Add Roundcube, a webmail
-=======================
+========================
 
 The NixOS module for roundcube nearly works out of the box with SNM. By
 default, it sets up a nginx virtual host to serve the webmail, other web
diff --git a/docs/conf.py b/docs/conf.py
index 84eb68b..1845917 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -18,7 +18,7 @@
 # -- Project information -----------------------------------------------------
 
 project = 'NixOS Mailserver'
-copyright = '2020, NixOS Mailserver Contributors'
+copyright = '2022, NixOS Mailserver Contributors'
 author = 'NixOS Mailserver Contributors'
 
 
@@ -28,8 +28,16 @@ author = 'NixOS Mailserver Contributors'
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
+    'myst_parser'
 ]
 
+myst_enable_extensions = [
+    'colon_fence',
+    'linkify',
+]
+
+smartquotes = False
+
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
 
@@ -50,4 +58,4 @@ html_theme = 'sphinx_rtd_theme'
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = []
diff --git a/docs/howto-develop.rst b/docs/howto-develop.rst
index ce74683..0ac9009 100644
--- a/docs/howto-develop.rst
+++ b/docs/howto-develop.rst
@@ -30,8 +30,8 @@ run tests manually. For instance:
 Contributing to the documentation
 ---------------------------------
 
-The documentation is written in RST, build with Sphinx and published
-by `Read the Docs <https://readthedocs.org/>`_.
+The documentation is written in RST (except option documentation which is in MarkDown),
+built with Sphinx and published by `Read the Docs <https://readthedocs.org/>`_.
 
 For the syntax, see `RST/Sphinx Cheatsheet
 <https://sphinx-tutorial.readthedocs.io/cheatsheet/>`_.
@@ -47,11 +47,11 @@ documentation:
    $ firefox ./_build/html/index.html
 
 Note if you modify some NixOS mailserver options, you would also need
-to regenerate the ``options.rst`` file:
+to regenerate the ``options.md`` file:
 
 ::
 
-   $ nix-shell --run generate-rst-options
+   $ nix-shell --run generate-options
 
 Nixops
 ------
diff --git a/docs/options.md b/docs/options.md
new file mode 100644
index 0000000..0944fa7
--- /dev/null
+++ b/docs/options.md
@@ -0,0 +1,1202 @@
+
+# Mailserver options
+
+## `mailserver`
+
+
+
+`````{option} mailserver.debug
+Whether to enable verbose logging for mailserver related services. This
+intended be used for development purposes only, you probably don't want
+to enable this unless you're hacking on nixos-mailserver.
+
+
+- type: ```boolean```
+- default: ```False```
+
+`````
+
+
+`````{option} mailserver.domains
+The domains that this mail server serves.
+
+- type: ```list of string```
+- default: ```[]```
+- example: ```['example.com']```
+`````
+
+
+`````{option} mailserver.enable
+Whether to enable nixos-mailserver.
+
+- type: ```boolean```
+- default: ```False```
+- example: ```True```
+`````
+
+
+`````{option} mailserver.enableImap
+Whether to enable IMAP with STARTTLS on port 143.
+
+
+- type: ```boolean```
+- default: ```True```
+
+`````
+
+
+`````{option} mailserver.enableImapSsl
+Whether to enable IMAP with TLS in wrapper-mode on port 993.
+
+
+- type: ```boolean```
+- default: ```True```
+
+`````
+
+
+`````{option} mailserver.enableManageSieve
+Whether to enable ManageSieve, setting this option to true will open
+port 4190 in the firewall.
+
+The ManageSieve protocol allows users to manage their Sieve scripts on
+a remote server with a supported client, including Thunderbird.
+
+
+- type: ```boolean```
+- default: ```False```
+
+`````
+
+
+`````{option} mailserver.enablePop3
+Whether to enable POP3 with STARTTLS on port on port 110.
+
+
+- type: ```boolean```
+- default: ```False```
+
+`````
+
+
+`````{option} mailserver.enablePop3Ssl
+Whether to enable POP3 with TLS in wrapper-mode on port 995.
+
+
+- type: ```boolean```
+- default: ```False```
+
+`````
+
+
+`````{option} mailserver.enableSubmission
+Whether to enable SMTP with STARTTLS on port 587.
+
+
+- type: ```boolean```
+- default: ```True```
+
+`````
+
+
+`````{option} mailserver.enableSubmissionSsl
+Whether to enable SMTP with TLS in wrapper-mode on port 465.
+
+
+- type: ```boolean```
+- default: ```True```
+
+`````
+
+
+`````{option} mailserver.extraVirtualAliases
+Virtual Aliases. A virtual alias `"info@example.com" = "user1@example.com"` means that
+all mail to `info@example.com` is forwarded to `user1@example.com`. Note
+that it is expected that `postmaster@example.com` and `abuse@example.com` is
+forwarded to some valid email address. (Alternatively you can create login
+accounts for `postmaster` and (or) `abuse`). Furthermore, it also allows
+the user `user1@example.com` to send emails as `info@example.com`.
+It's also possible to create an alias for multiple accounts. In this
+example all mails for `multi@example.com` will be forwarded to both
+`user1@example.com` and `user2@example.com`.
+
+
+- type: ```attribute set of ((Login Account) or non-empty (list of (Login Account)))```
+- default: ```{}```
+- example: ```{'abuse@example.com': 'user1@example.com', 'info@example.com': 'user1@example.com', 'multi@example.com': ['user1@example.com', 'user2@example.com'], 'postmaster@example.com': 'user1@example.com'}```
+`````
+
+
+`````{option} mailserver.forwards
+To forward mails to an external address. For instance,
+the value {`"user@example.com" = "user@elsewhere.com";}`
+means that mails to `user@example.com` are forwarded to
+`user@elsewhere.com`. The difference with the
+{option}`mailserver.extraVirtualAliases` option is that `user@elsewhere.com`
+can't send mail as `user@example.com`. Also, this option
+allows to forward mails to external addresses.
+
+
+- type: ```attribute set of ((list of string) or string)```
+- default: ```{}```
+- example: ```{'user@example.com': 'user@elsewhere.com'}```
+`````
+
+
+`````{option} mailserver.fqdn
+The fully qualified domain name of the mail server.
+
+- type: ```string```
+
+- example: ```mx.example.com```
+`````
+
+
+`````{option} mailserver.hierarchySeparator
+The hierarchy separator for mailboxes used by dovecot for the namespace 'inbox'.
+Dovecot defaults to "." but recommends "/".
+This affects how mailboxes appear to mail clients and sieve scripts.
+For instance when using "." then in a sieve script "example.com" would refer to the mailbox "com" in the parent mailbox "example".
+This does not determine the way your mails are stored on disk.
+See https://wiki.dovecot.org/Namespaces for details.
+
+
+- type: ```string```
+- default: ```.```
+
+`````
+
+
+`````{option} mailserver.indexDir
+Folder to store search indices. If null, indices are stored
+along with email, which could not necessarily be desirable,
+especially when {option}`mailserver.fullTextSearch.enable` is `true` since
+indices it creates are voluminous and do not need to be backed
+up.
+
+Be careful when changing this option value since all indices
+would be recreated at the new location (and clients would need
+to resynchronize).
+
+Note the some variables can be used in the file path. See
+https://doc.dovecot.org/configuration_manual/mail_location/#variables
+for details.
+
+
+- type: ```null or string```
+- default: ```None```
+- example: ```/var/lib/dovecot/indices```
+`````
+
+
+`````{option} mailserver.keyFile
+Scheme 1)
+Location of the key file
+
+
+- type: ```path```
+
+- example: ```/root/mail-server.key```
+`````
+
+
+`````{option} mailserver.lmtpSaveToDetailMailbox
+If an email address is delimited by a "+", should it be filed into a
+mailbox matching the string after the "+"?  For example,
+user1+test@example.com would be filed into the mailbox "test".
+
+
+- type: ```one of "yes", "no"```
+- default: ```yes```
+
+`````
+
+
+`````{option} mailserver.localDnsResolver
+Runs a local DNS resolver (kresd) as recommended when running rspamd. This prevents your log file from filling up with rspamd_monitored_dns_mon entries.
+
+
+- type: ```boolean```
+- default: ```True```
+
+`````
+
+
+`````{option} mailserver.mailDirectory
+Where to store the mail.
+
+
+- type: ```path```
+- default: ```/var/vmail```
+
+`````
+
+
+`````{option} mailserver.mailboxes
+The mailboxes for dovecot.
+Depending on the mail client used it might be necessary to change some mailbox's name.
+
+
+- type: ```unspecified value```
+- default: ```{'Drafts': {'auto': 'subscribe', 'specialUse': 'Drafts'}, 'Junk': {'auto': 'subscribe', 'specialUse': 'Junk'}, 'Sent': {'auto': 'subscribe', 'specialUse': 'Sent'}, 'Trash': {'auto': 'no', 'specialUse': 'Trash'}}```
+
+`````
+
+
+`````{option} mailserver.maxConnectionsPerUser
+Maximum number of IMAP/POP3 connections allowed for a user from each IP address.
+E.g. a value of 50 allows for 50 IMAP and 50 POP3 connections at the same
+time for a single user.
+
+
+- type: ```signed integer```
+- default: ```100```
+
+`````
+
+
+`````{option} mailserver.messageSizeLimit
+Message size limit enforced by Postfix.
+
+- type: ```signed integer```
+- default: ```20971520```
+- example: ```52428800```
+`````
+
+
+`````{option} mailserver.openFirewall
+Automatically open ports in the firewall.
+
+- type: ```boolean```
+- default: ```True```
+
+`````
+
+
+`````{option} mailserver.policydSPFExtraConfig
+Extra configuration options for policyd-spf. This can be use to among
+other things skip spf checking for some IP addresses.
+
+
+- type: ```strings concatenated with "\n"```
+- default: `""`
+- example: 
+```
+skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1
+```
+`````
+
+
+`````{option} mailserver.rebootAfterKernelUpgrade.enable
+Whether to enable automatic reboot after kernel upgrades.
+This is to be used in conjunction with `system.autoUpgrade.enable = true;`
+
+
+- type: ```boolean```
+- default: ```False```
+- example: ```True```
+`````
+
+
+`````{option} mailserver.rebootAfterKernelUpgrade.method
+Whether to issue a full "reboot" or just a "systemctl kexec"-only reboot.
+It is recommended to use the default value because the quicker kexec reboot has a number of problems.
+Also if your server is running in a virtual machine the regular reboot will already be very quick.
+
+
+- type: ```one of "reboot", "systemctl kexec"```
+- default: ```reboot```
+
+`````
+
+
+`````{option} mailserver.recipientDelimiter
+Configure the recipient delimiter.
+
+
+- type: ```string```
+- default: ```+```
+
+`````
+
+
+`````{option} mailserver.rejectRecipients
+Reject emails addressed to these local addresses from unauthorized senders.
+Use if a spammer has found email addresses in a catchall domain but you do
+not want to disable the catchall.
+
+
+- type: ```list of string```
+- default: ```[]```
+- example: ```['sales@example.com', 'info@example.com']```
+`````
+
+
+`````{option} mailserver.rejectSender
+Reject emails from these addresses from unauthorized senders.
+Use if a spammer is using the same domain or the same sender over and over.
+
+
+- type: ```list of string```
+- default: ```[]```
+- example: ```['@example.com', 'spammer@example.net']```
+`````
+
+
+`````{option} mailserver.rewriteMessageId
+Rewrites the Message-ID's hostname-part of outgoing emails to the FQDN.
+Please be aware that this may cause problems with some mail clients
+relying on the original Message-ID.
+
+
+- type: ```boolean```
+- default: ```False```
+
+`````
+
+
+`````{option} mailserver.sendingFqdn
+The fully qualified domain name of the mail server used to
+identify with remote servers.
+
+If this server's IP serves purposes other than a mail server,
+it may be desirable for the server to have a name other than
+that to which the user will connect.  For example, the user
+might connect to mx.example.com, but the server's IP has
+reverse DNS that resolves to myserver.example.com; in this
+scenario, some mail servers may reject or penalize the
+message.
+
+This setting allows the server to identify as
+myserver.example.com when forwarding mail, independently of
+{option}`mailserver.fqdn` (which, for SSL reasons, should generally be the name
+to which the user connects).
+
+Set this to the name to which the sending IP's reverse DNS
+resolves.
+
+
+- type: ```string```
+- default: {option}`mailserver.fqdn`
+- example: ```myserver.example.com```
+`````
+
+
+`````{option} mailserver.sieveDirectory
+Where to store the sieve scripts.
+
+
+- type: ```path```
+- default: ```/var/sieve```
+
+`````
+
+
+`````{option} mailserver.useFsLayout
+Sets whether dovecot should organize mail in subdirectories:
+
+- /var/vmail/example.com/user/.folder.subfolder/ (default layout)
+- /var/vmail/example.com/user/folder/subfolder/  (FS layout)
+
+See https://wiki2.dovecot.org/MailboxFormat/Maildir for details.
+
+
+- type: ```boolean```
+- default: ```False```
+
+`````
+
+
+`````{option} mailserver.virusScanning
+Whether to activate virus scanning. Note that virus scanning is _very_
+expensive memory wise.
+
+
+- type: ```boolean```
+- default: ```False```
+
+`````
+
+
+`````{option} mailserver.vmailGroupName
+The user name and group name of the user that owns the directory where all
+the mail is stored.
+
+
+- type: ```string```
+- default: ```virtualMail```
+
+`````
+
+
+`````{option} mailserver.vmailUID
+The unix UID of the virtual mail user.  Be mindful that if this is
+changed, you will need to manually adjust the permissions of
+`mailDirectory`.
+
+
+- type: ```signed integer```
+- default: ```5000```
+
+`````
+
+
+`````{option} mailserver.vmailUserName
+The user name and group name of the user that owns the directory where all
+the mail is stored.
+
+
+- type: ```string```
+- default: ```virtualMail```
+
+`````
+
+## `mailserver.loginAccounts`
+
+
+`````{option} mailserver.loginAccounts
+The login account of the domain. Every account is mapped to a unix user,
+e.g. `user1@example.com`. To generate the passwords use `mkpasswd` as
+follows
+
+```
+nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
+```
+
+
+- type: ```attribute set of (submodule)```
+- default: ```{}```
+- example: ```{'user1': {'hashedPassword': '$6$evQJs5CFQyPAW09S$Cn99Y8.QjZ2IBnSu4qf1vBxDRWkaIZWOtmu1Ddsm3.H3CFpeVc0JU4llIq8HQXgeatvYhh5O33eWG3TSpjzu6/'}, 'user2': {'hashedPassword': '$6$oE0ZNv2n7Vk9gOf$9xcZWCCLGdMflIfuA0vR1Q1Xblw6RZqPrP94mEit2/81/7AKj2bqUai5yPyWE.QYPyv6wLMHZvjw3Rlg7yTCD/'}}```
+`````
+
+
+`````{option} mailserver.loginAccounts.<name>.aliases
+A list of aliases of this login account.
+Note: Use list entries like "@example.com" to create a catchAll
+that allows sending from all email addresses in these domain.
+
+
+- type: ```list of string```
+- default: ```[]```
+- example: ```['abuse@example.com', 'postmaster@example.com']```
+`````
+
+
+`````{option} mailserver.loginAccounts.<name>.catchAll
+For which domains should this account act as a catch all?
+Note: Does not allow sending from all addresses of these domains.
+
+
+- type: ```list of value "example.com" (singular enum)```
+- default: ```[]```
+- example: ```['example.com', 'example2.com']```
+`````
+
+
+`````{option} mailserver.loginAccounts.<name>.hashedPassword
+The user's hashed password. Use `mkpasswd` as follows
+
+```
+nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
+```
+
+Warning: this is stored in plaintext in the Nix store!
+Use {option}`mailserver.loginAccounts.<name>.hashedPasswordFile` instead.
+
+
+- type: ```null or string```
+- default: ```None```
+- example: ```$6$evQJs5CFQyPAW09S$Cn99Y8.QjZ2IBnSu4qf1vBxDRWkaIZWOtmu1Ddsm3.H3CFpeVc0JU4llIq8HQXgeatvYhh5O33eWG3TSpjzu6/```
+`````
+
+
+`````{option} mailserver.loginAccounts.<name>.hashedPasswordFile
+A file containing the user's hashed password. Use `mkpasswd` as follows
+
+```
+nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
+```
+
+
+- type: ```null or path```
+- default: ```None```
+- example: ```/run/keys/user1-passwordhash```
+`````
+
+
+`````{option} mailserver.loginAccounts.<name>.name
+Username
+
+- type: ```string```
+
+- example: ```user1@example.com```
+`````
+
+
+`````{option} mailserver.loginAccounts.<name>.quota
+Per user quota rules. Accepted sizes are `xx k/M/G/T` with the
+obvious meaning. Leave blank for the standard quota `100G`.
+
+
+- type: ```null or string```
+- default: ```None```
+- example: ```2G```
+`````
+
+
+`````{option} mailserver.loginAccounts.<name>.sendOnly
+Specifies if the account should be a send-only account.
+Emails sent to send-only accounts will be rejected from
+unauthorized senders with the `sendOnlyRejectMessage`
+stating the reason.
+
+
+- type: ```boolean```
+- default: ```False```
+
+`````
+
+
+`````{option} mailserver.loginAccounts.<name>.sendOnlyRejectMessage
+The message that will be returned to the sender when an email is
+sent to a send-only account. Only used if the account is marked
+as send-only.
+
+
+- type: ```string```
+- default: ```This account cannot receive emails.```
+
+`````
+
+
+`````{option} mailserver.loginAccounts.<name>.sieveScript
+Per-user sieve script.
+
+
+- type: ```null or strings concatenated with "\n"```
+- default: ```None```
+- example: 
+```
+require ["fileinto", "mailbox"];
+
+if address :is "from" "gitlab@mg.gitlab.com" {
+  fileinto :create "GitLab";
+  stop;
+}
+
+# This must be the last rule, it will check if list-id is set, and
+# file the message into the Lists folder for further investigation
+elsif header :matches "list-id" "<?*>" {
+  fileinto :create "Lists";
+  stop;
+}
+```
+`````
+
+## `mailserver.certificate`
+
+
+`````{option} mailserver.certificateDirectory
+Scheme 2)
+This is the folder where the certificate will be created. The name is
+hardcoded to "cert-DOMAIN.pem" and "key-DOMAIN.pem" and the
+certificate is valid for 10 years.
+
+
+- type: ```path```
+- default: ```/var/certs```
+
+`````
+
+
+`````{option} mailserver.certificateDomains
+Secondary domains and subdomains for which it is necessary to generate a certificate.
+
+- type: ```list of string```
+- default: ```[]```
+- example: ```['imap.example.com', 'pop3.example.com']```
+`````
+
+
+`````{option} mailserver.certificateFile
+Scheme 1)
+Location of the certificate
+
+
+- type: ```path```
+
+- example: ```/root/mail-server.crt```
+`````
+
+
+`````{option} mailserver.certificateScheme
+Certificate Files. There are three options for these.
+
+1) You specify locations and manually copy certificates there.
+2) You let the server create new (self signed) certificates on the fly.
+3) You let the server create a certificate via `Let's Encrypt`. Note that
+   this implies that a stripped down webserver has to be started. This also
+   implies that the FQDN must be set as an `A` record to point to the IP of
+   the server. In particular port 80 on the server will be opened. For details
+   on how to set up the domain records, see the guide in the readme.
+
+
+- type: ```one of 1, 2, 3```
+- default: ```2```
+
+`````
+
+## `mailserver.dkim`
+
+
+`````{option} mailserver.dkimBodyCanonicalization
+DKIM canonicalization algorithm for message bodies.
+
+See https://datatracker.ietf.org/doc/html/rfc6376/#section-3.4 for details.
+
+
+- type: ```one of "relaxed", "simple"```
+- default: ```relaxed```
+
+`````
+
+
+`````{option} mailserver.dkimHeaderCanonicalization
+DKIM canonicalization algorithm for message headers.
+
+See https://datatracker.ietf.org/doc/html/rfc6376/#section-3.4 for details.
+
+
+- type: ```one of "relaxed", "simple"```
+- default: ```relaxed```
+
+`````
+
+
+`````{option} mailserver.dkimKeyBits
+How many bits in generated DKIM keys. RFC6376 advises minimum 1024-bit keys.
+
+If you have already deployed a key with a different number of bits than specified
+here, then you should use a different selector ({option}`mailserver.dkimSelector`). In order to get
+this package to generate a key with the new number of bits, you will either have to
+change the selector or delete the old key file.
+
+
+- type: ```signed integer```
+- default: ```1024```
+
+`````
+
+
+`````{option} mailserver.dkimKeyDirectory
+The DKIM directory.
+
+
+- type: ```path```
+- default: ```/var/dkim```
+
+`````
+
+
+`````{option} mailserver.dkimSelector
+The DKIM selector.
+
+
+- type: ```string```
+- default: ```mail```
+
+`````
+
+
+`````{option} mailserver.dkimSigning
+Whether to activate dkim signing.
+
+
+- type: ```boolean```
+- default: ```True```
+
+`````
+
+## `mailserver.dmarcReporting`
+
+
+`````{option} mailserver.dmarcReporting.domain
+The domain from which outgoing DMARC reports are served.
+
+
+- type: ```value "example.com" (singular enum)```
+
+- example: ```example.com```
+`````
+
+
+`````{option} mailserver.dmarcReporting.email
+The email address used for outgoing DMARC reports. Read-only.
+
+
+- type: ```string```
+- default: ```"${localpart}@${domain}"```
+
+`````
+
+
+`````{option} mailserver.dmarcReporting.enable
+Whether to send out aggregated, daily DMARC reports in response to incoming
+mail, when the sender domain defines a DMARC policy including the RUA tag.
+
+This is helpful for the mail ecosystem, because it allows third parties to
+get notified about SPF/DKIM violations originating from their sender domains.
+
+See https://rspamd.com/doc/modules/dmarc.html#reporting
+
+
+- type: ```boolean```
+- default: ```False```
+
+`````
+
+
+`````{option} mailserver.dmarcReporting.fromName
+The sender name for DMARC reports. Defaults to the organization name.
+
+
+- type: ```string```
+- default: {option}`mailserver.dmarcReporting.organizationName`
+
+`````
+
+
+`````{option} mailserver.dmarcReporting.localpart
+The local part of the email address used for outgoing DMARC reports.
+
+
+- type: ```string```
+- default: ```dmarc-noreply```
+- example: ```dmarc-report```
+`````
+
+
+`````{option} mailserver.dmarcReporting.organizationName
+The name of your organization used in the `org_name` attribute in
+DMARC reports.
+
+
+- type: ```string```
+
+- example: ```ACME Corp.```
+`````
+
+## `mailserver.fullTextSearch`
+
+
+`````{option} mailserver.fullTextSearch.autoIndex
+Enable automatic indexing of messages as they are received or modified.
+
+- type: ```boolean```
+- default: ```True```
+
+`````
+
+
+`````{option} mailserver.fullTextSearch.autoIndexExclude
+Mailboxes to exclude from automatic indexing.
+
+
+- type: ```list of string```
+- default: ```[]```
+- example: ```['\\Trash', 'SomeFolder', 'Other/*']```
+`````
+
+
+`````{option} mailserver.fullTextSearch.enable
+Whether to enable Full text search indexing with xapian. This has significant performance and disk space cost..
+
+- type: ```boolean```
+- default: ```False```
+- example: ```True```
+`````
+
+
+`````{option} mailserver.fullTextSearch.enforced
+Fail searches when no index is available. If set to
+`body`, then only body searches (as opposed to
+header) are affected. If set to `no`, searches may
+fall back to a very slow brute force search.
+
+
+- type: ```one of "yes", "no", "body"```
+- default: ```no```
+
+`````
+
+
+`````{option} mailserver.fullTextSearch.indexAttachments
+Also index text-only attachements. Binary attachements are never indexed.
+
+- type: ```boolean```
+- default: ```False```
+
+`````
+
+
+`````{option} mailserver.fullTextSearch.maintenance.enable
+Regularly optmize indices, as recommended by upstream.
+
+- type: ```boolean```
+- default: ```True```
+
+`````
+
+
+`````{option} mailserver.fullTextSearch.maintenance.onCalendar
+When to run the maintenance job. See systemd.time(7) for more information about the format.
+
+- type: ```string```
+- default: ```daily```
+
+`````
+
+
+`````{option} mailserver.fullTextSearch.maintenance.randomizedDelaySec
+Run the maintenance job not exactly at the time specified with `onCalendar`, but plus or minus this many seconds.
+
+- type: ```signed integer```
+- default: ```1000```
+
+`````
+
+
+`````{option} mailserver.fullTextSearch.maxSize
+Size of the largest n-gram to index.
+
+- type: ```signed integer```
+- default: ```20```
+
+`````
+
+
+`````{option} mailserver.fullTextSearch.memoryLimit
+Memory limit for the indexer process, in MiB. If null, leaves the default (which is rather low), and if 0, no limit.
+
+- type: ```null or signed integer```
+- default: ```None```
+- example: ```2000```
+`````
+
+
+`````{option} mailserver.fullTextSearch.minSize
+Size of the smallest n-gram to index.
+
+- type: ```signed integer```
+- default: ```2```
+
+`````
+
+## `mailserver.redis`
+
+
+`````{option} mailserver.redis.address
+Address that rspamd should use to contact redis.
+
+
+- type: ```string```
+- default: computed from `config.services.redis.servers.rspamd.bind`
+
+`````
+
+
+`````{option} mailserver.redis.password
+Password that rspamd should use to contact redis, or null if not required.
+
+
+- type: ```null or string```
+- default: ```config.services.redis.servers.rspamd.requirePass```
+
+`````
+
+
+`````{option} mailserver.redis.port
+Port that rspamd should use to contact redis.
+
+
+- type: ```16 bit unsigned integer; between 0 and 65535 (both inclusive)```
+- default: ```config.services.redis.servers.rspamd.port```
+
+`````
+
+## `mailserver.monitoring`
+
+
+`````{option} mailserver.monitoring.alertAddress
+The email address to send alerts to.
+
+
+- type: ```string```
+
+
+`````
+
+
+`````{option} mailserver.monitoring.config
+The configuration used for monitoring via monit.
+Use a mail address that you actively check and set it via 'set alert ...'.
+
+
+- type: ```string```
+- default: see [source](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/blob/master/default.nix)
+
+`````
+
+
+`````{option} mailserver.monitoring.enable
+Whether to enable monitoring via monit.
+
+- type: ```boolean```
+- default: ```False```
+- example: ```True```
+`````
+
+## `mailserver.backup`
+
+
+`````{option} mailserver.backup.cmdPostexec
+The command to be executed after each backup operation. This is wrapped in a shell script to be called by rsnapshot.
+
+- type: ```null or string```
+- default: ```None```
+
+`````
+
+
+`````{option} mailserver.backup.cmdPreexec
+The command to be executed before each backup operation. This is wrapped in a shell script to be called by rsnapshot.
+
+
+- type: ```null or string```
+- default: ```None```
+
+`````
+
+
+`````{option} mailserver.backup.cronIntervals
+Periodicity at which intervals should be run by cron.
+Note that the intervals also have to exist in configuration
+as retain options.
+
+
+- type: ```attribute set of string```
+- default: ```{'daily': '30  3  *  *  *', 'hourly': ' 0  *  *  *  *', 'weekly': ' 0  5  *  *  0'}```
+
+`````
+
+
+`````{option} mailserver.backup.enable
+Whether to enable backup via rsnapshot.
+
+- type: ```boolean```
+- default: ```False```
+- example: ```True```
+`````
+
+
+`````{option} mailserver.backup.retain.daily
+How many daily snapshots are retained.
+
+- type: ```signed integer```
+- default: ```7```
+
+`````
+
+
+`````{option} mailserver.backup.retain.hourly
+How many hourly snapshots are retained.
+
+- type: ```signed integer```
+- default: ```24```
+
+`````
+
+
+`````{option} mailserver.backup.retain.weekly
+How many weekly snapshots are retained.
+
+- type: ```signed integer```
+- default: ```54```
+
+`````
+
+
+`````{option} mailserver.backup.snapshotRoot
+The directory where rsnapshot stores the backup.
+
+
+- type: ```path```
+- default: ```/var/rsnapshot```
+
+`````
+
+## `mailserver.borgbackup`
+
+
+`````{option} mailserver.borgbackup.cmdPostexec
+The command to be executed after each backup operation.
+This is called after borg create completed successfully and in the same script that runs
+`cmdPreexec`, borg init and create.
+
+
+- type: ```null or string```
+- default: ```None```
+
+`````
+
+
+`````{option} mailserver.borgbackup.cmdPreexec
+The command to be executed before each backup operation.
+This is called prior to borg init in the same script that runs borg init and create and `cmdPostexec`.
+
+
+- type: ```null or string```
+- default: ```None```
+- example: 
+```
+export BORG_RSH="ssh -i /path/to/private/key"
+```
+`````
+
+
+`````{option} mailserver.borgbackup.compression.auto
+Leaves it to borg to determine whether an individual file should be compressed.
+
+- type: ```boolean```
+- default: ```False```
+
+`````
+
+
+`````{option} mailserver.borgbackup.compression.level
+Denotes the level of compression used by borg.
+Most methods accept levels from 0 to 9 but zstd which accepts values from 1 to 22.
+If null the decision is left up to borg.
+
+
+- type: ```null or signed integer```
+- default: ```None```
+
+`````
+
+
+`````{option} mailserver.borgbackup.compression.method
+Leaving this unset allows borg to choose. The default for borg 1.1.4 is lz4.
+
+- type: ```null or one of "none", "lz4", "zstd", "zlib", "lzma"```
+- default: ```None```
+
+`````
+
+
+`````{option} mailserver.borgbackup.enable
+Whether to enable backup via borgbackup.
+
+- type: ```boolean```
+- default: ```False```
+- example: ```True```
+`````
+
+
+`````{option} mailserver.borgbackup.encryption.method
+The backup can be encrypted by choosing any other value than 'none'.
+When using encryption the password/passphrase must be provided in `passphraseFile`.
+
+
+- type: ```one of "none", "authenticated", "authenticated-blake2", "repokey", "keyfile", "repokey-blake2", "keyfile-blake2"```
+- default: ```none```
+
+`````
+
+
+`````{option} mailserver.borgbackup.encryption.passphraseFile
+Path to a file containing the encryption password or passphrase.
+
+- type: ```null or path```
+- default: ```None```
+
+`````
+
+
+`````{option} mailserver.borgbackup.extraArgumentsForCreate
+Additional arguments to add to the borg create command line e.g. '--stats'.
+
+- type: ```list of string```
+- default: ```[]```
+
+`````
+
+
+`````{option} mailserver.borgbackup.extraArgumentsForInit
+Additional arguments to add to the borg init command line.
+
+- type: ```list of string```
+- default: ```['--critical']```
+
+`````
+
+
+`````{option} mailserver.borgbackup.group
+The group borg and its launch script is run as.
+
+- type: ```string```
+- default: ```virtualMail```
+
+`````
+
+
+`````{option} mailserver.borgbackup.locations
+The locations that are to be backed up by borg.
+
+- type: ```list of path```
+- default: ```[ config.mailserver.mailDirectory ]```
+
+`````
+
+
+`````{option} mailserver.borgbackup.name
+The name of the individual backups as used by borg.
+Certain placeholders will be replaced by borg.
+
+
+- type: ```string```
+- default: ```{hostname}-{user}-{now}```
+
+`````
+
+
+`````{option} mailserver.borgbackup.repoLocation
+The location where borg saves the backups.
+This can be a local path or a remote location such as user@host:/path/to/repo.
+It is exported and thus available as an environment variable to
+{option}`mailserver.borgbackup.cmdPreexec` and {option}`mailserver.borgbackup.cmdPostexec`.
+
+
+- type: ```string```
+- default: ```/var/borgbackup```
+
+`````
+
+
+`````{option} mailserver.borgbackup.startAt
+When or how often the backup should run. Must be in the format described in systemd.time 7.
+
+- type: ```string```
+- default: ```hourly```
+
+`````
+
+
+`````{option} mailserver.borgbackup.user
+The user borg and its launch script is run as.
+
+- type: ```string```
+- default: ```virtualMail```
+
+`````
+
diff --git a/docs/options.rst b/docs/options.rst
deleted file mode 100644
index b8aef82..0000000
--- a/docs/options.rst
+++ /dev/null
@@ -1,1319 +0,0 @@
-
-Mailserver Options
-==================
-
-mailserver
-~~~~~~~~~~
-
-
-
-mailserver.debug
-----------------
-
-Whether to enable verbose logging for mailserver related services. This
-intended be used for development purposes only, you probably don't want
-to enable this unless you're hacking on nixos-mailserver.
-
-
-- type: ``boolean``
-- default: ``False``
-
-
-
-mailserver.domains
-------------------
-
-The domains that this mail server serves.
-
-- type: ``list of string``
-- default: ``[]``
-- example: ``['example.com']``
-
-
-mailserver.enable
------------------
-
-Whether to enable nixos-mailserver.
-
-- type: ``boolean``
-- default: ``False``
-- example: ``True``
-
-
-mailserver.enableImap
----------------------
-
-Whether to enable IMAP with STARTTLS on port 143.
-
-
-- type: ``boolean``
-- default: ``True``
-
-
-
-mailserver.enableImapSsl
-------------------------
-
-Whether to enable IMAP with TLS in wrapper-mode on port 993.
-
-
-- type: ``boolean``
-- default: ``True``
-
-
-
-mailserver.enableManageSieve
-----------------------------
-
-Whether to enable ManageSieve, setting this option to true will open
-port 4190 in the firewall.
-
-The ManageSieve protocol allows users to manage their Sieve scripts on
-a remote server with a supported client, including Thunderbird.
-
-
-- type: ``boolean``
-- default: ``False``
-
-
-
-mailserver.enablePop3
----------------------
-
-Whether to enable POP3 with STARTTLS on port on port 110.
-
-
-- type: ``boolean``
-- default: ``False``
-
-
-
-mailserver.enablePop3Ssl
-------------------------
-
-Whether to enable POP3 with TLS in wrapper-mode on port 995.
-
-
-- type: ``boolean``
-- default: ``False``
-
-
-
-mailserver.enableSubmission
----------------------------
-
-Whether to enable SMTP with STARTTLS on port 587.
-
-
-- type: ``boolean``
-- default: ``True``
-
-
-
-mailserver.enableSubmissionSsl
-------------------------------
-
-Whether to enable SMTP with TLS in wrapper-mode on port 465.
-
-
-- type: ``boolean``
-- default: ``True``
-
-
-
-mailserver.extraVirtualAliases
-------------------------------
-
-Virtual Aliases. A virtual alias `"info@example.com" = "user1@example.com"` means that
-all mail to `info@example.com` is forwarded to `user1@example.com`. Note
-that it is expected that `postmaster@example.com` and `abuse@example.com` is
-forwarded to some valid email address. (Alternatively you can create login
-accounts for `postmaster` and (or) `abuse`). Furthermore, it also allows
-the user `user1@example.com` to send emails as `info@example.com`.
-It's also possible to create an alias for multiple accounts. In this
-example all mails for `multi@example.com` will be forwarded to both
-`user1@example.com` and `user2@example.com`.
-
-
-- type: ``attribute set of ((Login Account) or non-empty (list of (Login Account)))``
-- default: ``{}``
-- example: ``{'abuse@example.com': 'user1@example.com', 'info@example.com': 'user1@example.com', 'multi@example.com': ['user1@example.com', 'user2@example.com'], 'postmaster@example.com': 'user1@example.com'}``
-
-
-mailserver.forwards
--------------------
-
-To forward mails to an external address. For instance,
-the value {`"user@example.com" = "user@elsewhere.com";}`
-means that mails to `user@example.com` are forwarded to
-`user@elsewhere.com`. The difference with the
-`extraVirtualAliases` option is that `user@elsewhere.com`
-can't send mail as `user@example.com`. Also, this option
-allows to forward mails to external addresses.
-
-
-- type: ``attribute set of ((list of string) or string)``
-- default: ``{}``
-- example: ``{'user@example.com': 'user@elsewhere.com'}``
-
-
-mailserver.fqdn
----------------
-
-The fully qualified domain name of the mail server.
-
-- type: ``string``
-
-- example: ``mx.example.com``
-
-
-mailserver.hierarchySeparator
------------------------------
-
-The hierarchy separator for mailboxes used by dovecot for the namespace 'inbox'.
-Dovecot defaults to "." but recommends "/".
-This affects how mailboxes appear to mail clients and sieve scripts.
-For instance when using "." then in a sieve script "example.com" would refer to the mailbox "com" in the parent mailbox "example".
-This does not determine the way your mails are stored on disk.
-See https://wiki.dovecot.org/Namespaces for details.
-
-
-- type: ``string``
-- default: ``.``
-
-
-
-mailserver.indexDir
--------------------
-
-Folder to store search indices. If null, indices are stored
-along with email, which could not necessarily be desirable,
-especially when the fullTextSearch option is enable since
-indices it creates are voluminous and do not need to be backed
-up.
-
-Be careful when changing this option value since all indices
-would be recreated at the new location (and clients would need
-to resynchronize).
-
-Note the some variables can be used in the file path. See
-https://doc.dovecot.org/configuration_manual/mail_location/#variables
-for details.
-
-
-- type: ``null or string``
-- default: ``None``
-- example: ``/var/lib/dovecot/indices``
-
-
-mailserver.keyFile
-------------------
-
-Scheme 1)
-Location of the key file
-
-
-- type: ``path``
-
-- example: ``/root/mail-server.key``
-
-
-mailserver.lmtpSaveToDetailMailbox
-----------------------------------
-
-If an email address is delimited by a "+", should it be filed into a
-mailbox matching the string after the "+"?  For example,
-user1+test@example.com would be filed into the mailbox "test".
-
-
-- type: ``one of "yes", "no"``
-- default: ``yes``
-
-
-
-mailserver.localDnsResolver
----------------------------
-
-Runs a local DNS resolver (kresd) as recommended when running rspamd. This prevents your log file from filling up with rspamd_monitored_dns_mon entries.
-
-
-- type: ``boolean``
-- default: ``True``
-
-
-
-mailserver.mailDirectory
-------------------------
-
-Where to store the mail.
-
-
-- type: ``path``
-- default: ``/var/vmail``
-
-
-
-mailserver.mailboxes
---------------------
-
-The mailboxes for dovecot.
-Depending on the mail client used it might be necessary to change some mailbox's name.
-
-
-- type: ``unspecified value``
-- default: ``{'Drafts': {'auto': 'subscribe', 'specialUse': 'Drafts'}, 'Junk': {'auto': 'subscribe', 'specialUse': 'Junk'}, 'Sent': {'auto': 'subscribe', 'specialUse': 'Sent'}, 'Trash': {'auto': 'no', 'specialUse': 'Trash'}}``
-
-
-
-mailserver.maxConnectionsPerUser
---------------------------------
-
-Maximum number of IMAP/POP3 connections allowed for a user from each IP address.
-E.g. a value of 50 allows for 50 IMAP and 50 POP3 connections at the same
-time for a single user.
-
-
-- type: ``signed integer``
-- default: ``100``
-
-
-
-mailserver.messageSizeLimit
----------------------------
-
-Message size limit enforced by Postfix.
-
-- type: ``signed integer``
-- default: ``20971520``
-- example: ``52428800``
-
-
-mailserver.openFirewall
------------------------
-
-Automatically open ports in the firewall.
-
-- type: ``boolean``
-- default: ``True``
-
-
-
-mailserver.policydSPFExtraConfig
---------------------------------
-
-Extra configuration options for policyd-spf. This can be use to among
-other things skip spf checking for some IP addresses.
-
-
-- type: ``strings concatenated with "\n"``
-- default: ``""``
-- example: 
-.. code::
-
-  skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1
-
-
-
-
-mailserver.rebootAfterKernelUpgrade.enable
-------------------------------------------
-
-Whether to enable automatic reboot after kernel upgrades.
-This is to be used in conjunction with system.autoUpgrade.enable = true"
-
-
-- type: ``boolean``
-- default: ``False``
-- example: ``True``
-
-
-mailserver.rebootAfterKernelUpgrade.method
-------------------------------------------
-
-Whether to issue a full "reboot" or just a "systemctl kexec"-only reboot.
-It is recommended to use the default value because the quicker kexec reboot has a number of problems.
-Also if your server is running in a virtual machine the regular reboot will already be very quick.
-
-
-- type: ``one of "reboot", "systemctl kexec"``
-- default: ``reboot``
-
-
-
-mailserver.recipientDelimiter
------------------------------
-
-Configure the recipient delimiter.
-
-
-- type: ``string``
-- default: ``+``
-
-
-
-mailserver.rejectRecipients
----------------------------
-
-Reject emails addressed to these local addresses from unauthorized senders.
-Use if a spammer has found email addresses in a catchall domain but you do
-not want to disable the catchall.
-
-
-- type: ``list of string``
-- default: ``[]``
-- example: ``['sales@example.com', 'info@example.com']``
-
-
-mailserver.rejectSender
------------------------
-
-Reject emails from these addresses from unauthorized senders.
-Use if a spammer is using the same domain or the same sender over and over.
-
-
-- type: ``list of string``
-- default: ``[]``
-- example: ``['@example.com', 'spammer@example.net']``
-
-
-mailserver.rewriteMessageId
----------------------------
-
-Rewrites the Message-ID's hostname-part of outgoing emails to the FQDN.
-Please be aware that this may cause problems with some mail clients
-relying on the original Message-ID.
-
-
-- type: ``boolean``
-- default: ``False``
-
-
-
-mailserver.sendingFqdn
-----------------------
-
-The fully qualified domain name of the mail server used to
-identify with remote servers.
-
-If this server's IP serves purposes other than a mail server,
-it may be desirable for the server to have a name other than
-that to which the user will connect.  For example, the user
-might connect to mx.example.com, but the server's IP has
-reverse DNS that resolves to myserver.example.com; in this
-scenario, some mail servers may reject or penalize the
-message.
-
-This setting allows the server to identify as
-myserver.example.com when forwarding mail, independently of
-`fqdn` (which, for SSL reasons, should generally be the name
-to which the user connects).
-
-Set this to the name to which the sending IP's reverse DNS
-resolves.
-
-
-- type: ``string``
-- default: ``config.mailserver.fqdn``
-- example: ``myserver.example.com``
-
-
-mailserver.sieveDirectory
--------------------------
-
-Where to store the sieve scripts.
-
-
-- type: ``path``
-- default: ``/var/sieve``
-
-
-
-mailserver.useFsLayout
-----------------------
-
-Sets whether dovecot should organize mail in subdirectories:
-
-- /var/vmail/example.com/user/.folder.subfolder/ (default layout)
-- /var/vmail/example.com/user/folder/subfolder/  (FS layout)
-
-See https://wiki2.dovecot.org/MailboxFormat/Maildir for details.
-
-
-- type: ``boolean``
-- default: ``False``
-
-
-
-mailserver.virusScanning
-------------------------
-
-Whether to activate virus scanning. Note that virus scanning is _very_
-expensive memory wise.
-
-
-- type: ``boolean``
-- default: ``False``
-
-
-
-mailserver.vmailGroupName
--------------------------
-
-The user name and group name of the user that owns the directory where all
-the mail is stored.
-
-
-- type: ``string``
-- default: ``virtualMail``
-
-
-
-mailserver.vmailUID
--------------------
-
-The unix UID of the virtual mail user.  Be mindful that if this is
-changed, you will need to manually adjust the permissions of
-mailDirectory.
-
-
-- type: ``signed integer``
-- default: ``5000``
-
-
-
-mailserver.vmailUserName
-------------------------
-
-The user name and group name of the user that owns the directory where all
-the mail is stored.
-
-
-- type: ``string``
-- default: ``virtualMail``
-
-
-mailserver.loginAccount
-~~~~~~~~~~~~~~~~~~~~~~~
-
-
-mailserver.loginAccounts
-------------------------
-
-The login account of the domain. Every account is mapped to a unix user,
-e.g. `user1@example.com`. To generate the passwords use `mkpasswd` as
-follows
-
-```
-nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
-```
-
-
-- type: ``attribute set of (submodule)``
-- default: ``{}``
-- example: ``{'user1': {'hashedPassword': '$6$evQJs5CFQyPAW09S$Cn99Y8.QjZ2IBnSu4qf1vBxDRWkaIZWOtmu1Ddsm3.H3CFpeVc0JU4llIq8HQXgeatvYhh5O33eWG3TSpjzu6/'}, 'user2': {'hashedPassword': '$6$oE0ZNv2n7Vk9gOf$9xcZWCCLGdMflIfuA0vR1Q1Xblw6RZqPrP94mEit2/81/7AKj2bqUai5yPyWE.QYPyv6wLMHZvjw3Rlg7yTCD/'}}``
-
-
-mailserver.loginAccounts.<name>.aliases
----------------------------------------
-
-A list of aliases of this login account.
-Note: Use list entries like "@example.com" to create a catchAll
-that allows sending from all email addresses in these domain.
-
-
-- type: ``list of string``
-- default: ``[]``
-- example: ``['abuse@example.com', 'postmaster@example.com']``
-
-
-mailserver.loginAccounts.<name>.catchAll
-----------------------------------------
-
-For which domains should this account act as a catch all?
-Note: Does not allow sending from all addresses of these domains.
-
-
-- type: ``list of value "example.com" (singular enum)``
-- default: ``[]``
-- example: ``['example.com', 'example2.com']``
-
-
-mailserver.loginAccounts.<name>.hashedPassword
-----------------------------------------------
-
-The user's hashed password. Use `mkpasswd` as follows
-
-```
-nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
-```
-
-Warning: this is stored in plaintext in the Nix store!
-Use `hashedPasswordFile` instead.
-
-
-- type: ``null or string``
-- default: ``None``
-- example: ``$6$evQJs5CFQyPAW09S$Cn99Y8.QjZ2IBnSu4qf1vBxDRWkaIZWOtmu1Ddsm3.H3CFpeVc0JU4llIq8HQXgeatvYhh5O33eWG3TSpjzu6/``
-
-
-mailserver.loginAccounts.<name>.hashedPasswordFile
---------------------------------------------------
-
-A file containing the user's hashed password. Use `mkpasswd` as follows
-
-```
-nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
-```
-
-
-- type: ``null or path``
-- default: ``None``
-- example: ``/run/keys/user1-passwordhash``
-
-
-mailserver.loginAccounts.<name>.name
-------------------------------------
-
-Username
-
-- type: ``string``
-
-- example: ``user1@example.com``
-
-
-mailserver.loginAccounts.<name>.quota
--------------------------------------
-
-Per user quota rules. Accepted sizes are `xx k/M/G/T` with the
-obvious meaning. Leave blank for the standard quota `100G`.
-
-
-- type: ``null or string``
-- default: ``None``
-- example: ``2G``
-
-
-mailserver.loginAccounts.<name>.sendOnly
-----------------------------------------
-
-Specifies if the account should be a send-only account.
-Emails sent to send-only accounts will be rejected from
-unauthorized senders with the sendOnlyRejectMessage
-stating the reason.
-
-
-- type: ``boolean``
-- default: ``False``
-
-
-
-mailserver.loginAccounts.<name>.sendOnlyRejectMessage
------------------------------------------------------
-
-The message that will be returned to the sender when an email is
-sent to a send-only account. Only used if the account is marked
-as send-only.
-
-
-- type: ``string``
-- default: ``This account cannot receive emails.``
-
-
-
-mailserver.loginAccounts.<name>.sieveScript
--------------------------------------------
-
-Per-user sieve script.
-
-
-- type: ``null or strings concatenated with "\n"``
-- default: ``None``
-- example: 
-.. code::
-
-  require ["fileinto", "mailbox"];
-
-  if address :is "from" "gitlab@mg.gitlab.com" {
-    fileinto :create "GitLab";
-    stop;
-  }
-
-  # This must be the last rule, it will check if list-id is set, and
-  # file the message into the Lists folder for further investigation
-  elsif header :matches "list-id" "<?*>" {
-    fileinto :create "Lists";
-    stop;
-  }
-
-
-
-mailserver.certificate
-~~~~~~~~~~~~~~~~~~~~~~
-
-
-mailserver.certificateDirectory
--------------------------------
-
-Scheme 2)
-This is the folder where the certificate will be created. The name is
-hardcoded to "cert-DOMAIN.pem" and "key-DOMAIN.pem" and the
-certificate is valid for 10 years.
-
-
-- type: ``path``
-- default: ``/var/certs``
-
-
-
-mailserver.certificateDomains
------------------------------
-
-Secondary domains and subdomains for which it is necessary to generate a certificate.
-
-- type: ``list of string``
-- default: ``[]``
-- example: ``['imap.example.com', 'pop3.example.com']``
-
-
-mailserver.certificateFile
---------------------------
-
-Scheme 1)
-Location of the certificate
-
-
-- type: ``path``
-
-- example: ``/root/mail-server.crt``
-
-
-mailserver.certificateScheme
-----------------------------
-
-Certificate Files. There are three options for these.
-
-1) You specify locations and manually copy certificates there.
-2) You let the server create new (self signed) certificates on the fly.
-3) You let the server create a certificate via `Let's Encrypt`. Note that
-   this implies that a stripped down webserver has to be started. This also
-   implies that the FQDN must be set as an `A` record to point to the IP of
-   the server. In particular port 80 on the server will be opened. For details
-   on how to set up the domain records, see the guide in the readme.
-
-
-- type: ``one of 1, 2, 3``
-- default: ``2``
-
-
-mailserver.dkim
-~~~~~~~~~~~~~~~
-
-
-mailserver.dkimBodyCanonicalization
------------------------------------
-
-DKIM canonicalization algorithm for message bodies.
-
-See https://datatracker.ietf.org/doc/html/rfc6376/#section-3.4 for details.
-
-
-- type: ``one of "relaxed", "simple"``
-- default: ``relaxed``
-
-
-
-mailserver.dkimHeaderCanonicalization
--------------------------------------
-
-DKIM canonicalization algorithm for message headers.
-
-See https://datatracker.ietf.org/doc/html/rfc6376/#section-3.4 for details.
-
-
-- type: ``one of "relaxed", "simple"``
-- default: ``relaxed``
-
-
-
-mailserver.dkimKeyBits
-----------------------
-
-How many bits in generated DKIM keys. RFC6376 advises minimum 1024-bit keys.
-
-If you have already deployed a key with a different number of bits than specified
-here, then you should use a different selector (dkimSelector). In order to get
-this package to generate a key with the new number of bits, you will either have to
-change the selector or delete the old key file.
-
-
-- type: ``signed integer``
-- default: ``1024``
-
-
-
-mailserver.dkimKeyDirectory
----------------------------
-
-
-
-
-- type: ``path``
-- default: ``/var/dkim``
-
-
-
-mailserver.dkimSelector
------------------------
-
-
-
-
-- type: ``string``
-- default: ``mail``
-
-
-
-mailserver.dkimSigning
-----------------------
-
-Whether to activate dkim signing.
-
-
-- type: ``boolean``
-- default: ``True``
-
-
-mailserver.dmarcReporting
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
-mailserver.dmarcReporting.domain
---------------------------------
-
-The domain from which outgoing DMARC reports are served.
-
-
-- type: ``value "example.com" (singular enum)``
-
-- example: ``example.com``
-
-
-mailserver.dmarcReporting.email
--------------------------------
-
-The email address used for outgoing DMARC reports. Read-only.
-
-
-- type: ``string``
-- default: ``"${localpart}@${domain}"``
-
-
-
-mailserver.dmarcReporting.enable
---------------------------------
-
-Whether to send out aggregated, daily DMARC reports in response to incoming
-mail, when the sender domain defines a DMARC policy including the RUA tag.
-
-This is helpful for the mail ecosystem, because it allows third parties to
-get notified about SPF/DKIM violations originating from their sender domains.
-
-See https://rspamd.com/doc/modules/dmarc.html#reporting
-
-
-- type: ``boolean``
-- default: ``False``
-
-
-
-mailserver.dmarcReporting.fromName
-----------------------------------
-
-The sender name for DMARC reports. Defaults to the organization name.
-
-
-- type: ``string``
-- default: ``organizationName``
-
-
-
-mailserver.dmarcReporting.localpart
------------------------------------
-
-The local part of the email address used for outgoing DMARC reports.
-
-
-- type: ``string``
-- default: ``dmarc-noreply``
-- example: ``dmarc-report``
-
-
-mailserver.dmarcReporting.organizationName
-------------------------------------------
-
-The name of your organization used in the <literal>org_name</literal> attribute in
-DMARC reports.
-
-
-- type: ``string``
-
-- example: ``ACME Corp.``
-
-mailserver.fullTextSearch
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
-mailserver.fullTextSearch.autoIndex
------------------------------------
-
-Enable automatic indexing of messages as they are received or modified.
-
-- type: ``boolean``
-- default: ``True``
-
-
-
-mailserver.fullTextSearch.autoIndexExclude
-------------------------------------------
-
-Mailboxes to exclude from automatic indexing.
-
-
-- type: ``list of string``
-- default: ``[]``
-- example: ``['\\Trash', 'SomeFolder', 'Other/*']``
-
-
-mailserver.fullTextSearch.enable
---------------------------------
-
-Whether to enable Full text search indexing with xapian. This has significant performance and disk space cost..
-
-- type: ``boolean``
-- default: ``False``
-- example: ``True``
-
-
-mailserver.fullTextSearch.enforced
-----------------------------------
-
-Fail searches when no index is available. If set to
-<literal>body</literal>, then only body searches (as opposed to
-header) are affected. If set to <literal>no</literal>, searches may
-fall back to a very slow brute force search.
-
-
-- type: ``one of "yes", "no", "body"``
-- default: ``no``
-
-
-
-mailserver.fullTextSearch.indexAttachments
-------------------------------------------
-
-Also index text-only attachements. Binary attachements are never indexed.
-
-- type: ``boolean``
-- default: ``False``
-
-
-
-mailserver.fullTextSearch.maintenance.enable
---------------------------------------------
-
-Regularly optmize indices, as recommended by upstream.
-
-- type: ``boolean``
-- default: ``True``
-
-
-
-mailserver.fullTextSearch.maintenance.onCalendar
-------------------------------------------------
-
-When to run the maintenance job. See systemd.time(7) for more information about the format.
-
-- type: ``string``
-- default: ``daily``
-
-
-
-mailserver.fullTextSearch.maintenance.randomizedDelaySec
---------------------------------------------------------
-
-Run the maintenance job not exactly at the time specified with <literal>onCalendar</literal>, but plus or minus this many seconds.
-
-- type: ``signed integer``
-- default: ``1000``
-
-
-
-mailserver.fullTextSearch.maxSize
----------------------------------
-
-Size of the largest n-gram to index.
-
-- type: ``signed integer``
-- default: ``20``
-
-
-
-mailserver.fullTextSearch.memoryLimit
--------------------------------------
-
-Memory limit for the indexer process, in MiB. If null, leaves the default (which is rather low), and if 0, no limit.
-
-- type: ``null or signed integer``
-- default: ``None``
-- example: ``2000``
-
-
-mailserver.fullTextSearch.minSize
----------------------------------
-
-Size of the smallest n-gram to index.
-
-- type: ``signed integer``
-- default: ``2``
-
-
-mailserver.redis
-~~~~~~~~~~~~~~~~
-
-
-mailserver.redis.address
-------------------------
-
-Address that rspamd should use to contact redis.
-
-
-- type: ``string``
-- default: computed from <option>config.services.redis.servers.rspamd.bind</option>
-
-
-
-mailserver.redis.password
--------------------------
-
-Password that rspamd should use to contact redis, or null if not required.
-
-
-- type: ``null or string``
-- default: ``config.services.redis.servers.rspamd.requirePass``
-
-
-
-mailserver.redis.port
----------------------
-
-Port that rspamd should use to contact redis.
-
-
-- type: ``16 bit unsigned integer; between 0 and 65535 (both inclusive)``
-- default: ``config.services.redis.servers.rspamd.port``
-
-
-mailserver.monitoring
-~~~~~~~~~~~~~~~~~~~~~
-
-
-mailserver.monitoring.alertAddress
-----------------------------------
-
-The email address to send alerts to.
-
-
-- type: ``string``
-
-
-
-
-mailserver.monitoring.config
-----------------------------
-
-The configuration used for monitoring via monit.
-Use a mail address that you actively check and set it via 'set alert ...'.
-
-
-- type: ``string``
-- default: see source
-
-
-
-mailserver.monitoring.enable
-----------------------------
-
-Whether to enable monitoring via monit.
-
-- type: ``boolean``
-- default: ``False``
-- example: ``True``
-
-mailserver.backup
-~~~~~~~~~~~~~~~~~
-
-
-mailserver.backup.cmdPostexec
------------------------------
-
-The command to be executed after each backup operation. This is wrapped in a shell script to be called by rsnapshot.
-
-- type: ``null or string``
-- default: ``None``
-
-
-
-mailserver.backup.cmdPreexec
-----------------------------
-
-The command to be executed before each backup operation. This is wrapped in a shell script to be called by rsnapshot.
-
-
-- type: ``null or string``
-- default: ``None``
-
-
-
-mailserver.backup.cronIntervals
--------------------------------
-
-Periodicity at which intervals should be run by cron.
-Note that the intervals also have to exist in configuration
-as retain options.
-
-
-- type: ``attribute set of string``
-- default: ``{'daily': '30  3  *  *  *', 'hourly': ' 0  *  *  *  *', 'weekly': ' 0  5  *  *  0'}``
-
-
-
-mailserver.backup.enable
-------------------------
-
-Whether to enable backup via rsnapshot.
-
-- type: ``boolean``
-- default: ``False``
-- example: ``True``
-
-
-mailserver.backup.retain.daily
-------------------------------
-
-How many daily snapshots are retained.
-
-- type: ``signed integer``
-- default: ``7``
-
-
-
-mailserver.backup.retain.hourly
--------------------------------
-
-How many hourly snapshots are retained.
-
-- type: ``signed integer``
-- default: ``24``
-
-
-
-mailserver.backup.retain.weekly
--------------------------------
-
-How many weekly snapshots are retained.
-
-- type: ``signed integer``
-- default: ``54``
-
-
-
-mailserver.backup.snapshotRoot
-------------------------------
-
-The directory where rsnapshot stores the backup.
-
-
-- type: ``path``
-- default: ``/var/rsnapshot``
-
-
-mailserver.borg
-~~~~~~~~~~~~~~~
-
-
-mailserver.borgbackup.cmdPostexec
----------------------------------
-
-The command to be executed after each backup operation.
-This is called after borg create completed successfully and in the same script that runs
-cmdPreexec, borg init and create.
-
-
-- type: ``null or string``
-- default: ``None``
-
-
-
-mailserver.borgbackup.cmdPreexec
---------------------------------
-
-The command to be executed before each backup operation.
-This is called prior to borg init in the same script that runs borg init and create and cmdPostexec.
-Example:
-  export BORG_RSH="ssh -i /path/to/private/key"
-
-
-- type: ``null or string``
-- default: ``None``
-
-
-
-mailserver.borgbackup.compression.auto
---------------------------------------
-
-Leaves it to borg to determine whether an individual file should be compressed.
-
-- type: ``boolean``
-- default: ``False``
-
-
-
-mailserver.borgbackup.compression.level
----------------------------------------
-
-Denotes the level of compression used by borg.
-Most methods accept levels from 0 to 9 but zstd which accepts values from 1 to 22.
-If null the decision is left up to borg.
-
-
-- type: ``null or signed integer``
-- default: ``None``
-
-
-
-mailserver.borgbackup.compression.method
-----------------------------------------
-
-Leaving this unset allows borg to choose. The default for borg 1.1.4 is lz4.
-
-- type: ``null or one of "none", "lz4", "zstd", "zlib", "lzma"``
-- default: ``None``
-
-
-
-mailserver.borgbackup.enable
-----------------------------
-
-Whether to enable backup via borgbackup.
-
-- type: ``boolean``
-- default: ``False``
-- example: ``True``
-
-
-mailserver.borgbackup.encryption.method
----------------------------------------
-
-The backup can be encrypted by choosing any other value than 'none'.
-When using encryption the password / passphrase must be provided in passphraseFile.
-
-
-- type: ``one of "none", "authenticated", "authenticated-blake2", "repokey", "keyfile", "repokey-blake2", "keyfile-blake2"``
-- default: ``none``
-
-
-
-mailserver.borgbackup.encryption.passphraseFile
------------------------------------------------
-
-Path to a file containing the encryption password or passphrase.
-
-- type: ``null or path``
-- default: ``None``
-
-
-
-mailserver.borgbackup.extraArgumentsForCreate
----------------------------------------------
-
-Additional arguments to add to the borg create command line e.g. '--stats'.
-
-- type: ``list of string``
-- default: ``[]``
-
-
-
-mailserver.borgbackup.extraArgumentsForInit
--------------------------------------------
-
-Additional arguments to add to the borg init command line.
-
-- type: ``list of string``
-- default: ``['--critical']``
-
-
-
-mailserver.borgbackup.group
----------------------------
-
-The group borg and its launch script is run as.
-
-- type: ``string``
-- default: ``virtualMail``
-
-
-
-mailserver.borgbackup.locations
--------------------------------
-
-The locations that are to be backed up by borg.
-
-- type: ``list of path``
-- default: ``['/var/vmail']``
-
-
-
-mailserver.borgbackup.name
---------------------------
-
-The name of the individual backups as used by borg.
-Certain placeholders will be replaced by borg.
-
-
-- type: ``string``
-- default: ``{hostname}-{user}-{now}``
-
-
-
-mailserver.borgbackup.repoLocation
-----------------------------------
-
-The location where borg saves the backups.
-This can be a local path or a remote location such as user@host:/path/to/repo.
-It is exported and thus available as an environment variable to cmdPreexec and cmdPostexec.
-
-
-- type: ``string``
-- default: ``/var/borgbackup``
-
-
-
-mailserver.borgbackup.startAt
------------------------------
-
-When or how often the backup should run. Must be in the format described in systemd.time 7.
-
-- type: ``string``
-- default: ``hourly``
-
-
-
-mailserver.borgbackup.user
---------------------------
-
-The user borg and its launch script is run as.
-
-- type: ``string``
-- default: ``virtualMail``
-
-
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 1b9a4f8..2211dd5 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,2 +1,4 @@
-sphinx==4.0.2
-sphinx_rtd_theme==0.5.2
+sphinx ~= 5.3
+sphinx_rtd_theme ~= 1.1
+myst-parser ~= 0.18
+linkify-it-py ~= 2.0
diff --git a/flake.lock b/flake.lock
index 0f7f71a..4b4474d 100644
--- a/flake.lock
+++ b/flake.lock
@@ -16,6 +16,22 @@
         "type": "gitlab"
       }
     },
+    "flake-compat": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1668681692,
+        "narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "009399224d5e398d03b22badca40a37ac85412a1",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
     "nixpkgs": {
       "locked": {
         "lastModified": 1669542132,
@@ -49,6 +65,7 @@
     "root": {
       "inputs": {
         "blobs": "blobs",
+        "flake-compat": "flake-compat",
         "nixpkgs": "nixpkgs",
         "nixpkgs-22_11": "nixpkgs-22_11",
         "utils": "utils"
diff --git a/flake.nix b/flake.nix
index 4b5c8e6..9a46ea3 100644
--- a/flake.nix
+++ b/flake.nix
@@ -2,6 +2,10 @@
   description = "A complete and Simple Nixos Mailserver";
 
   inputs = {
+    flake-compat = {
+      url = "github:edolstra/flake-compat";
+      flake = false;
+    };
     utils.url = "github:numtide/flake-utils";
     nixpkgs.url = "flake:nixpkgs/nixos-unstable";
     nixpkgs-22_11.url = "flake:nixpkgs/nixos-22.11";
@@ -11,7 +15,8 @@
     };
   };
 
-  outputs = { self, utils, blobs, nixpkgs, nixpkgs-22_11 }: let
+  outputs = { self, utils, blobs, nixpkgs, nixpkgs-22_11, ... }: let
+    lib = nixpkgs.lib;
     system = "x86_64-linux";
     pkgs = nixpkgs.legacyPackages.${system};
     releases = [
@@ -43,22 +48,18 @@
     #   external-21_05 = <derivation>;
     #   ...
     # }
-    allTests = pkgs.lib.listToAttrs (
-      pkgs.lib.flatten (map (t: map (r: genTest t r) releases) testNames));
+    allTests = lib.listToAttrs (
+      lib.flatten (map (t: map (r: genTest t r) releases) testNames));
 
     mailserverModule = import ./.;
 
-    # Generate a rst file describing options of the NixOS mailserver module
-    generateRstOptions = let
-      eval = import (pkgs.path + "/nixos/lib/eval-config.nix") {
-        inherit system;
+    # Generate a MarkDown file describing the options of the NixOS mailserver module
+    optionsDoc = let
+      eval = lib.evalModules {
         modules = [
           mailserverModule
           {
-            # Because the blockbook package is currently broken (we
-            # don't care about this package but it is part of the
-            # NixOS module evaluation)
-            nixpkgs.config.allowBroken = true;
+            _module.check = false;
             mailserver = {
               fqdn = "mx.example.com";
               domains = [
@@ -71,27 +72,26 @@
             };
           }
         ];
-
       };
-      options = pkgs.nixosOptionsDoc {
-        options = eval.options;
-      };
-    in pkgs.runCommand "options.rst" { buildInputs = [pkgs.python3]; } ''
-      echo Generating options.rst from ${options.optionsJSON}/share/doc/nixos/options.json
-      python ${./scripts/generate-rst-options.py} ${options.optionsJSON}/share/doc/nixos/options.json > $out
+      options = builtins.toFile "options.json" (builtins.toJSON
+        (lib.filter (opt: opt.visible && !opt.internal && lib.head opt.loc == "mailserver")
+          (lib.optionAttrSetToDocList eval.options)));
+    in pkgs.runCommand "options.md" { buildInputs = [pkgs.python3Minimal]; } ''
+      echo "Generating options.md from ${options}"
+      python ${./scripts/generate-options.py} ${options} > $out
     '';
 
     # This is a script helping users to generate this file in the docs directory
-    generateRstOptionsScript = pkgs.writeScriptBin "generate-rst-options" ''
-      cp -v ${generateRstOptions} ./docs/options.rst
+    generateOptions = pkgs.writeShellScriptBin "generate-options" ''
+      install -vm644 ${optionsDoc} ./docs/options.md
     '';
 
-    # This is to ensure we don't forget to update the options.rst file
-    testRstOptions = pkgs.runCommand "test-rst-options" {} ''
-      if ! diff -q ${./docs/options.rst} ${generateRstOptions}
+    # This is to ensure we don't forget to update the options.md file
+    testOptions = pkgs.runCommand "test-options" {} ''
+      if ! diff -q ${./docs/options.md} ${optionsDoc}
       then
-        echo "The file ./docs/options.rst is not up-to-date and needs to be regenerated!"
-        echo "  hint: run 'nix-shell --run generate-rst-options' to generate this file"
+        echo "The file ./docs/options.md is not up-to-date and needs to be regenerated!"
+        echo "  hint: run 'nix-shell --run generate-options' to generate this file"
         exit 1
       fi
       echo "test: ok" > $out
@@ -99,43 +99,43 @@
 
     documentation = pkgs.stdenv.mkDerivation {
       name = "documentation";
-      src = pkgs.lib.sourceByRegex ./docs ["logo.png" "conf.py" "Makefile" ".*rst$"];
+      src = lib.sourceByRegex ./docs ["logo\\.png" "conf\\.py" "Makefile" ".*\\.rst"];
       buildInputs = [(
-        pkgs.python3.withPackages(p: [
-          p.sphinx
-          p.sphinx_rtd_theme
+        pkgs.python3.withPackages (p: with p; [
+          sphinx
+          sphinx_rtd_theme
+          myst-parser
         ])
       )];
       buildPhase = ''
-        cp ${generateRstOptions} options.rst
-        mkdir -p _static
+        cp ${optionsDoc} options.md
         # Workaround for https://github.com/sphinx-doc/sphinx/issues/3451
-        export SOURCE_DATE_EPOCH=$(${pkgs.coreutils}/bin/date +%s)
+        unset SOURCE_DATE_EPOCH
         make html
       '';
       installPhase = ''
-        cp -r _build/html $out
+        cp -Tr _build/html $out
       '';
     };
 
-  in rec {
-    nixosModules.mailserver = mailserverModule ;
-    nixosModule = self.nixosModules.mailserver;
+  in {
+    nixosModules = rec {
+      mailserver = mailserverModule;
+      default = mailserver;
+    };
+    nixosModule = self.nixosModules.default; # compatibility
     hydraJobs.${system} = allTests // {
-      test-rst-options = testRstOptions;
+      test-options = testOptions;
       inherit documentation;
     };
     checks.${system} = allTests;
-    devShell.${system} = pkgs.mkShell {
-      buildInputs = with pkgs; [
-        generateRstOptionsScript
-        (python3.withPackages (p: with p; [
-          sphinx
-          sphinx_rtd_theme
-        ]))
-        jq
+    devShells.${system}.default = pkgs.mkShell {
+      inputsFrom = [ documentation ];
+      packages = with pkgs; [
+        generateOptions
         clamav
       ];
     };
+    devShell.${system} = self.devShells.${system}.default; # compatibility
   };
 }
diff --git a/scripts/generate-options.py b/scripts/generate-options.py
new file mode 100644
index 0000000..a4973b1
--- /dev/null
+++ b/scripts/generate-options.py
@@ -0,0 +1,81 @@
+import json
+import sys
+
+header = """
+# Mailserver options
+
+## `mailserver`
+
+"""
+
+template = """
+`````{{option}} {key}
+{description}
+
+{type}
+{default}
+{example}
+`````
+"""
+
+f = open(sys.argv[1])
+options = json.load(f)
+
+groups = ["mailserver.loginAccounts",
+          "mailserver.certificate",
+          "mailserver.dkim",
+          "mailserver.dmarcReporting",
+          "mailserver.fullTextSearch",
+          "mailserver.redis",
+          "mailserver.monitoring",
+          "mailserver.backup",
+          "mailserver.borgbackup"]
+
+def render_option_value(opt, attr):
+  if attr in opt:
+      if isinstance(opt[attr], dict) and '_type' in opt[attr]:
+          if opt[attr]['_type'] == 'literalExpression':
+              if '\n' in opt[attr]['text']:
+                  res = '\n```nix\n' + opt[attr]['text'].rstrip('\n') + '\n```'
+              else:
+                  res = '```{}```'.format(opt[attr]['text'])
+          elif opt[attr]['_type'] == 'literalMD':
+              res = opt[attr]['text']
+      else:
+          s = str(opt[attr])
+          if s == "":
+              res = '`""`'
+          elif '\n' in s:
+              res = '\n```\n' + s.rstrip('\n') + '\n```'
+          else:
+              res = '```{}```'.format(s)
+      res = '- ' + attr + ': ' + res
+  else:
+      res = ""
+  return res
+
+def print_option(opt):
+    if isinstance(opt['description'], dict) and '_type' in opt['description']: # mdDoc
+        description = opt['description']['text']
+    else:
+        description = opt['description']
+    print(template.format(
+        key=opt['name'],
+        description=description or "",
+        type="- type: ```{}```".format(opt['type']),
+        default=render_option_value(opt, 'default'),
+        example=render_option_value(opt, 'example')))
+
+
+print(header)
+for opt in options:
+    if any([opt['name'].startswith(c) for c in groups]):
+        continue
+    print_option(opt)
+
+for c in groups:
+    print('## `{}`'.format(c))
+    print()
+    for opt in options:
+        if opt['name'].startswith(c):
+            print_option(opt)
diff --git a/scripts/generate-rst-options.py b/scripts/generate-rst-options.py
deleted file mode 100644
index 8c5ce9c..0000000
--- a/scripts/generate-rst-options.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import json
-import sys
-import re
-import textwrap
-
-header = """
-Mailserver Options
-==================
-
-mailserver
-~~~~~~~~~~
-
-"""
-
-template = """
-{key}
-{line}
-
-{description}
-
-{type}
-{default}
-{example}
-"""
-
-f = open(sys.argv[1])
-options = json.load(f)
-
-options = {k: v for k, v in options.items()
-           if k.startswith("mailserver.")}
-
-groups = ["mailserver.loginAccount",
-          "mailserver.certificate",
-          "mailserver.dkim",
-          "mailserver.dmarcReporting",
-          "mailserver.fullTextSearch",
-          "mailserver.redis",
-          "mailserver.monitoring",
-          "mailserver.backup",
-          "mailserver.borg"]
-
-def render_option_value(opt, attr):
-  if attr in opt:
-      if isinstance(opt[attr], dict) and '_type' in opt[attr]:
-          if opt[attr]['_type'] == 'literalExpression':
-              if '\n' in opt[attr]['text']:
-                  res = '\n.. code:: nix\n\n' + textwrap.indent(opt[attr]['text'], '  ') + '\n'
-              else:
-                  res = '``{}``'.format(opt[attr]['text'])
-          elif opt[attr]['_type'] == 'literalDocBook':
-              res = opt[attr]['text']
-      else:
-          s = str(opt[attr])
-          if s == "":
-              res = '``""``'
-          elif '\n' in s:
-              res = '\n.. code::\n\n' + textwrap.indent(s, '  ') + '\n'
-          else:
-              res = '``{}``'.format(s)
-      res = '- ' + attr + ': ' + res
-  else:
-      res = ""
-  return res
-
-def print_option(name, value):
-    print(template.format(
-        key=name,
-        line="-"*len(name),
-        description=value['description'] or "",
-        type="- type: ``{}``".format(value['type']),
-        default=render_option_value(value, 'default'),
-        example=render_option_value(value, 'example')))
-
-
-print(header)
-for k, v in options.items():
-    if any([k.startswith(c) for c in groups]):
-        continue
-    print_option(k, v)
-
-for c in groups:
-    print(c)
-    print("~"*len(c))
-    print()
-    for k, v in options.items():
-        if k.startswith(c):
-            print_option(k, v)
diff --git a/shell.nix b/shell.nix
index d32886e..6234bb4 100644
--- a/shell.nix
+++ b/shell.nix
@@ -1 +1,10 @@
-(import (builtins.fetchGit "https://github.com/edolstra/flake-compat") { src = ./.; }).shellNix
+(import
+  (
+    let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
+    fetchTarball {
+      url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
+      sha256 = lock.nodes.flake-compat.locked.narHash;
+    }
+  )
+  { src = ./.; }
+).shellNix