infrastructure/modules/nixos/extranix/default.nix

277 lines
8.1 KiB
Nix
Raw Normal View History

# SPDX-FileCopyrightText: 2024 Lubin Bailly <lubin.bailly@dgnum.eu>
#
# SPDX-License-Identifier: EUPL-1.2
{
lib,
config,
pkgs,
...
}:
let
inherit (lib)
attrNames
concatMapStringsSep
concatStringsSep
escapeXML
filter
getExe
hasPrefix
hasSuffix
head
importJSON
mapAttrs'
mapAttrsToList
mkDefault
mkEnableOption
mkIf
mkOption
optionalString
pathIsDirectory
removePrefix
;
inherit (lib.strings)
sanitizeDerivationName
;
inherit (lib.types)
attrs
attrsOf
deferredModule
listOf
path
submodule
str
;
yaml = pkgs.formats.yaml { };
json = pkgs.formats.json { };
cfg = config.services.extranix;
module-eval =
module-name: module:
let
ignored-eval = lib.evalModules {
modules = module.ignored-modules;
inherit (module) specialArgs;
};
ignored-opts-doc = pkgs.nixosOptionsDoc { inherit (ignored-eval) options; };
ignored-opts = importJSON "${ignored-opts-doc.optionsJSON}/share/doc/nixos/options.json";
eval = lib.evalModules {
modules = module.paths ++ module.ignored-modules;
inherit (module) specialArgs;
};
opts-doc = pkgs.nixosOptionsDoc { inherit (eval) options; };
opts = importJSON "${opts-doc.optionsJSON}/share/doc/nixos/options.json";
filtered-opts = removeAttrs opts (attrNames ignored-opts);
path-translation =
let
translations = map (
{ base, url }:
{
url = "${url}${optionalString (!hasSuffix "/" url) "/"}";
base =
let
base1 = toString base;
in
base1 + (optionalString (!hasSuffix "/" base1) "/");
}
) module.path-translations;
in
path:
let
fullPath = path + (optionalString (pathIsDirectory path) "/default.nix");
fitting = filter ({ base, ... }: hasPrefix base fullPath) translations;
translate-info = head (
fitting
++ [
(throw (
"${fullPath} is not in any base path of ${module-name}. Base paths are "
+ concatMapStringsSep "\n" ({ base, ... }: base) translations
))
]
);
innerPath = removePrefix translate-info.base fullPath;
in
{
name = "<${innerPath}>";
url = "${translate-info.url}${innerPath}";
};
result' = json.generate "options-extranix-fileDesc.json" {
last_update = "-/-";
options = mapAttrsToList (title: val: {
inherit title;
inherit (val)
type
readOnly
loc
;
descriptionHTML = pkgs.runCommand "option-${title}.html" { } ''
${getExe pkgs.pandoc} -f markdown-raw_html ${pkgs.writeText "option-${title}.md" val.description} > $out
'';
description = escapeXML val.description;
example = escapeXML (val.example.text or "");
default = escapeXML (val.default.text or "");
declarations = map path-translation val.declarations;
}) filtered-opts;
};
result =
pkgs.runCommand "options-extranix.json"
{
nativeBuildInputs = [ pkgs.jq ];
}
''
jq -r '.options[].descriptionHTML | "--rawfile\n" + . + "\n" + .' ${result'} | xargs \
jq -c '.options |= map(.descriptionHTML as $desc | .descriptionHTML |= $ARGS.named.[$desc])' ${result'} \
> $out
'';
in
result;
options-files = mapAttrs' (name: value: {
name = sanitizeDerivationName name;
value = module-eval name value;
}) cfg.modules;
webroot = pkgs.callPackage ./webroot.nix {
inherit options-files;
inherit (cfg) static-data;
settings = yaml.generate "config.yaml" cfg.settings;
hugo-theme-extranix-options-search = pkgs.callPackage ./hugo-theme-extranix-options-search.nix { };
};
in
{
options.services.extranix = {
enable = mkEnableOption "extranix documentation";
modules = mkOption {
type =
attrsOf (submodule {
options = {
specialArgs = mkOption {
type = attrs;
default = { };
description = ''
Special arguments to give to evalModules.
'';
};
paths = mkOption {
type = listOf deferredModule;
description = ''
Modules to from which to document options.
'';
};
ignored-modules = mkOption {
type = listOf deferredModule;
default = [ ];
description = ''
Modules required to make modules of `paths` valid.
'';
example = ''
import "''${infra-modulesPath}/module-list.nix"
'';
};
path-translations = mkOption {
type = listOf (submodule {
options = {
base = mkOption {
type = path;
description = ''
Base path of some module files to be documented.
'';
};
url = mkOption {
type = str;
description = ''
Url root to use for files on this path.
'';
};
};
});
description = ''
Rules to convert file paths to urls in the documentation to indicate where
the option is declared.
'';
};
};
});
description = ''
Sets of modules to be documented separately. The identifier to give for
`settings.params.release_current_stable` (which is the default module shown) is the key after
passing through `sanitizeDerivationName`.
'';
};
settings = mkOption {
inherit (yaml) type;
description = ''
Settings for the `config.yaml` for the hugo instantiation.
'';
example = {
baseUrl = "https://example.org/";
title = "Module documentation";
languageCode = "en-us";
params = {
release_current_stable = "Some-Module";
logo = "logo.png";
footer_credits_line = ''
Based on <a href="https://github.com/mipmip/home-manager-option-search">Home Manager Option Search</a>
'';
footer_copyright_line = ''
Made by catvayor for the <a href="https://dgnum.eu">DGNum</a>.
'';
main_menu = [
{
name = ''Sources'';
url = "https://git.example.org/";
}
];
};
};
};
static-data = mkOption {
type = path;
description = ''
Static files for the website. Should have `images/favicon.png` for favicon.
'';
};
host = mkOption {
type = str;
description = ''
Hostname of the service.
'';
};
};
config = mkIf cfg.enable {
services = {
extranix.settings = {
theme = "extranix-options-search";
params = {
releases = mapAttrsToList (name: _: {
inherit name;
value = sanitizeDerivationName name;
}) cfg.modules;
release_current_stable = mkDefault (head (attrNames options-files));
};
};
nginx = {
enable = true;
virtualHosts.${cfg.host}.locations."/".alias = "${webroot}/";
};
};
assertions = [
{
assertion = cfg.modules != { };
message = ''
`services.extranix` can't be enabled without any modules to document.
'';
}
{
assertion = options-files ? ${cfg.settings.params.release_current_stable};
message = ''
`services.extranix.settings.params.release_current_stable` should be the
`sanitizeDerivationName` of a key of `services.extranix.modules`, here one of:
+ ${concatStringsSep "\n + " (attrNames options-files)}
'';
}
];
};
}