docs: use MarkDown for option docs

This commit is contained in:
Naïm Favier 2022-11-30 22:30:45 +01:00
parent bc667fb6af
commit 4fcab839d7
No known key found for this signature in database
GPG Key ID: 95AFCE8211908325
13 changed files with 1403 additions and 1487 deletions

View File

@ -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

View File

@ -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,8 +986,9 @@ 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:
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 {

View File

@ -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

View File

@ -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 = []

View File

@ -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
------

1202
docs/options.md Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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"

View File

@ -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
};
}

View File

@ -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)

View File

@ -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)

View File

@ -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