From ba08a453ea26e16e6205e22a38070976cc6f3283 Mon Sep 17 00:00:00 2001 From: technofab Date: Mon, 28 Jul 2025 20:41:17 +0200 Subject: [PATCH 1/3] feat: improve module by using freeformType and filtering unset options --- lib/module.nix | 96 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 23 deletions(-) diff --git a/lib/module.nix b/lib/module.nix index 4833971..b113ffb 100644 --- a/lib/module.nix +++ b/lib/module.nix @@ -4,7 +4,29 @@ config, ... }: let - inherit (lib) types mkOption; + inherit (lib) mkOptionType isType filterAttrs types mkOption; + + unsetType = mkOptionType { + name = "unset"; + description = "unset"; + descriptionClass = "noun"; + check = value: true; + }; + unset = { + _type = "unset"; + }; + isUnset = isType "unset"; + unsetOr = types.either unsetType; + + filterUnset = value: + if builtins.isAttrs value && !builtins.hasAttr "_type" value + then let + filteredAttrs = builtins.mapAttrs (n: v: filterUnset v) value; + in + filterAttrs (name: value: (!isUnset value)) filteredAttrs + else if builtins.isList value + then builtins.filter (elem: !isUnset elem) (map filterUnset value) + else value; collectionType = types.submodule { options = { @@ -18,6 +40,54 @@ }; }; }; + tasksType = types.submodule { + freeformType = types.attrsOf (types.attrsOf types.anything); + options = { + name = mkOption { + type = unsetOr types.str; + default = unset; + }; + register = mkOption { + type = unsetOr types.str; + default = unset; + }; + block = mkOption { + type = unsetOr (types.listOf tasksType); + default = unset; + }; + always = mkOption { + type = unsetOr (types.listOf types.attrs); + default = unset; + }; + }; + }; + playbookType = types.listOf (types.submodule { + options = { + name = mkOption { + type = types.str; + description = "Name of the play"; + }; + hosts = mkOption { + type = types.str; + description = "The target hosts for this play (e.g., 'all', 'webservers')"; + }; + become = mkOption { + type = unsetOr types.bool; + default = unset; + description = "Whether to use privilege escalation (become: yes)"; + }; + gather_facts = mkOption { + type = unsetOr types.bool; + default = unset; + description = ""; + }; + tasks = mkOption { + type = types.listOf tasksType; + default = []; + description = "List of tasks to execute in this play"; + }; + }; + }); in { options = { ansiblePackage = mkOption { @@ -36,28 +106,8 @@ in { description = "List of packages to include at runtime"; }; playbook = mkOption { - type = types.listOf (types.submodule { - options = { - name = mkOption { - type = types.str; - description = "Name of the play"; - }; - hosts = mkOption { - type = types.str; - description = "The target hosts for this play (e.g., 'all', 'webservers')"; - }; - become = mkOption { - type = types.bool; - default = false; - description = "Whether to use privilege escalation (become: yes)"; - }; - tasks = mkOption { - type = types.listOf types.attrs; - default = []; - description = "List of tasks to execute in this play"; - }; - }; - }); + type = playbookType; + apply = res: filterUnset res; description = "The actual playbook, defined as a Nix data structure"; }; From 8dccb9a9a84ba4542abc1d572d920471d3c33e51 Mon Sep 17 00:00:00 2001 From: technofab Date: Sat, 9 Aug 2025 20:49:11 +0200 Subject: [PATCH 2/3] feat: add more options and generate docs for all options --- docs/options.md | 3 + docs/reference.md | 130 +----------------------------- flake.lock | 7 +- flake.nix | 38 ++++++++- lib/module.nix | 201 ++++++++++++++++++++++++++++++++++++++-------- 5 files changed, 213 insertions(+), 166 deletions(-) create mode 100644 docs/options.md diff --git a/docs/options.md b/docs/options.md new file mode 100644 index 0000000..4ca74a4 --- /dev/null +++ b/docs/options.md @@ -0,0 +1,3 @@ +# Options + +{% include 'options.md' %} diff --git a/docs/reference.md b/docs/reference.md index 3ef3160..e9f7434 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -84,132 +84,6 @@ mkNixibleCli config Creates a CLI executable for your Nixible configuration. Basically `(mkNixible config).config.cli`. -## Configuration Options +______________________________________________________________________ -### `ansiblePackage` - -**Type:** `package` -**Default:** Custom ansible-core package - -The Ansible package to use. The default package is optimized for size, by not -including the gazillion collections that `pkgs.ansible` and `pkgs.ansible-core` include. - -```nix -ansiblePackage = pkgs.ansible; -``` - -### `collections` - -**Type:** `attrsOf collectionType` -**Default:** `{}` - -Ansible collections to fetch from Galaxy. - -```nix -collections = { - "community-general" = { - version = "8.0.0"; - hash = "sha256-..."; - }; -}; -``` - -### `dependencies` - -**Type:** `listOf package` -**Default:** `[]` - -Additional packages available at runtime. - -```nix -dependencies = [pkgs.git pkgs.rsync]; -``` - -### `inventory` - -**Type:** `attrs` -**Default:** `{}` - -Ansible inventory as Nix data structure, converted to JSON. - -```nix -inventory = { - webservers = { - hosts = { - web1 = { ansible_host = "192.168.1.10"; }; - }; - vars = { - http_port = 80; - }; - }; -}; -``` - -### `playbook` - -**Type:** `listOf playbookType` - -List of plays that make up the playbook. - -```nix -playbook = [ - { - name = "Configure servers"; - hosts = "webservers"; - become = true; - tasks = [ - { - name = "Install nginx"; - package = { - name = "nginx"; - state = "present"; - }; - } - ]; - } -]; -``` - -## Collection Type - -### `version` - -**Type:** `str` - -Version of the collection from Ansible Galaxy. - -### `hash` - -**Type:** `str` - -SHA256 hash of the collection tarball for verification. - -## Playbook Type - -### `name` - -**Type:** `str` - -Name of the play. - -### `hosts` - -**Type:** `str` - -Target hosts pattern (e.g., "all", "webservers", "localhost"). - -### `become` - -**Type:** `bool` -**Default:** `false` - -Whether to use privilege escalation. - -### `tasks` - -**Type:** `listOf attrs` -**Default:** `[]` - -List of tasks to execute. Each task corresponds to Ansible task syntax. - -Standard Ansible playbook options are supported: `gather_facts`, `serial`, `vars`, `vars_files`, `tags`, `handlers`, `pre_tasks`, `post_tasks`, etc. +See [options](./options.md) for more. diff --git a/flake.lock b/flake.lock index 15209f4..2e3eb9a 100644 --- a/flake.lock +++ b/flake.lock @@ -230,16 +230,17 @@ "nix-mkdocs": { "locked": { "dir": "lib", - "lastModified": 1745841841, - "narHash": "sha256-297zPQbUlc7ZAYDoaD6mCmQxCC3Tr4YOKekRF1ArZ7g=", + "lastModified": 1754753501, + "narHash": "sha256-k7KEGQ7qejmwrSXVSvZ9zVD1H+ZtNm0I5fmCVmZVi+4=", "owner": "technofab", "repo": "nixmkdocs", - "rev": "c7e3c3b13ded25818e9789938387bba6f2cde690", + "rev": "aba4f26f320b15043101824b65e72058ecab296f", "type": "gitlab" }, "original": { "dir": "lib", "owner": "technofab", + "ref": "v1.0.0", "repo": "nixmkdocs", "type": "gitlab" } diff --git a/flake.nix b/flake.nix index 7e10acf..1eca9f5 100644 --- a/flake.nix +++ b/flake.nix @@ -17,6 +17,7 @@ perSystem = { lib, pkgs, + self', config, ... }: { @@ -42,6 +43,7 @@ path = ./docs; deps = pp: [ pp.mkdocs-material + pp.mkdocs-macros (pp.callPackage inputs.mkdocs-material-umami {}) ]; config = { @@ -78,12 +80,21 @@ } ]; }; - plugins = ["search" "material-umami"]; + plugins = [ + "search" + "material-umami" + { + macros = { + include_dir = self'.packages.optionsDocs; + }; + } + ]; nav = [ {"Introduction" = "index.md";} {"Usage" = "usage.md";} {"Examples" = "examples.md";} {"Reference" = "reference.md";} + {"Options" = "options.md";} ]; markdown_extensions = [ "pymdownx.superfences" @@ -174,13 +185,34 @@ packages = let nblib = import ./lib {inherit pkgs lib;}; ntlib = inputs.nixtest.lib {inherit pkgs lib;}; - in { + doclib = inputs.nix-mkdocs.lib {inherit lib pkgs;}; + in rec { tests = ntlib.mkNixtest { modules = ntlib.autodiscover {dir = ./tests;}; args = { inherit pkgs nblib ntlib; }; }; + optionsDoc = doclib.mkOptionDocs { + module = { + imports = [ + nblib.module + { + _module.args.pkgs = pkgs; + } + ]; + }; + roots = [ + { + url = "https://gitlab.com/TECHNOFAB/nixible/-/blob/main/lib"; + path = toString ./lib; + } + ]; + }; + optionsDocs = pkgs.runCommand "options-docs" {} '' + mkdir -p $out + ln -s ${optionsDoc} $out/options.md + ''; }; }; }; @@ -195,7 +227,7 @@ treefmt-nix.url = "github:numtide/treefmt-nix"; nix-gitlab-ci.url = "gitlab:technofab/nix-gitlab-ci?dir=lib"; nixtest.url = "gitlab:technofab/nixtest?dir=lib"; - nix-mkdocs.url = "gitlab:technofab/nixmkdocs?dir=lib"; + nix-mkdocs.url = "gitlab:technofab/nixmkdocs/v1.0.0?dir=lib"; mkdocs-material-umami.url = "gitlab:technofab/mkdocs-material-umami"; }; diff --git a/lib/module.nix b/lib/module.nix index b113ffb..bcbe713 100644 --- a/lib/module.nix +++ b/lib/module.nix @@ -4,7 +4,7 @@ config, ... }: let - inherit (lib) mkOptionType isType filterAttrs types mkOption; + inherit (lib) mkOptionType isType filterAttrs types mkOption literalExpression; unsetType = mkOptionType { name = "unset"; @@ -16,7 +16,11 @@ _type = "unset"; }; isUnset = isType "unset"; - unsetOr = types.either unsetType; + unsetOr = typ: + (types.either unsetType typ) + // { + description = typ.description; + }; filterUnset = value: if builtins.isAttrs value && !builtins.hasAttr "_type" value @@ -28,93 +32,226 @@ then builtins.filter (elem: !isUnset elem) (map filterUnset value) else value; + mkUnsetOption = args: + mkOption args + // { + type = unsetOr args.type; + default = unset; + defaultText = literalExpression "unset"; + }; + collectionType = types.submodule { options = { version = mkOption { type = types.str; - description = "Version of collection"; + description = '' + Version of the collection. + ''; + example = "1.0.0"; }; hash = mkOption { type = types.str; - description = "Hash of the collection tarball"; + description = '' + SHA256 hash of the collection tarball for verification. + ''; + example = "sha256-..."; }; }; }; tasksType = types.submodule { freeformType = types.attrsOf (types.attrsOf types.anything); options = { - name = mkOption { - type = unsetOr types.str; - default = unset; + name = mkUnsetOption { + type = types.str; + description = '' + Name of the task. + ''; }; - register = mkOption { - type = unsetOr types.str; - default = unset; + register = mkUnsetOption { + type = types.str; + description = '' + Register the task's output to a variable. + ''; }; - block = mkOption { - type = unsetOr (types.listOf tasksType); - default = unset; + block = mkUnsetOption { + type = types.listOf tasksType; + description = '' + A block of tasks to execute. + ''; }; - always = mkOption { - type = unsetOr (types.listOf types.attrs); - default = unset; + rescue = mkUnsetOption { + type = types.listOf tasksType; + description = '' + A list of tasks to execute on failure of block tasks. + ''; + }; + always = mkUnsetOption { + type = types.listOf types.attrs; + description = '' + Tasks that always run, regardless of task status. + ''; + }; + delegate_to = mkUnsetOption { + type = types.str; + description = '' + Delegate task execution to another host. + ''; + }; + ignore_errors = mkUnsetOption { + type = types.bool; + description = '' + Ignore errors and continue with the playbook. + ''; + }; + loop = mkUnsetOption { + type = types.anything; + description = '' + Define a loop for the task. + ''; + }; + when = mkUnsetOption { + type = types.str; + description = '' + Condition under which the task runs. + ''; }; }; }; - playbookType = types.listOf (types.submodule { + playType = types.submodule { + freeformType = types.attrsOf (types.attrsOf types.anything); options = { name = mkOption { type = types.str; - description = "Name of the play"; + description = '' + Name of the play. + ''; }; hosts = mkOption { type = types.str; - description = "The target hosts for this play (e.g., 'all', 'webservers')"; + description = '' + The target hosts for this play (e.g., 'all', 'webservers'). + ''; + example = "all"; }; - become = mkOption { - type = unsetOr types.bool; - default = unset; - description = "Whether to use privilege escalation (become: yes)"; + remote_user = mkUnsetOption { + type = types.str; + description = '' + The user to execute tasks as on the remote server. + ''; }; - gather_facts = mkOption { - type = unsetOr types.bool; - default = unset; - description = ""; + tags = mkUnsetOption { + type = types.listOf types.str; + description = '' + Tags to filter tasks to run. + ''; + }; + become = mkUnsetOption { + type = types.bool; + description = '' + Whether to use privilege escalation (become: yes). + ''; + }; + become_method = mkUnsetOption { + type = types.str; + description = '' + Privilege escalation method. + ''; + }; + vars = mkUnsetOption { + type = types.attrs; + description = '' + Variables for the play. + ''; + }; + gather_facts = mkUnsetOption { + type = types.either types.bool types.str; + description = '' + Whether to run the setup module to gather facts before executing tasks. + ''; + }; + when = mkUnsetOption { + type = types.str; + description = '' + Condition under which the play runs. + ''; }; tasks = mkOption { type = types.listOf tasksType; default = []; - description = "List of tasks to execute in this play"; + description = '' + List of tasks to execute in this play + ''; }; }; - }); + }; + playbookType = types.listOf playType; in { options = { ansiblePackage = mkOption { type = types.package; default = pkgs.python3Packages.callPackage ./ansible-core.nix {}; - description = "Ansible package to use (default doesn't have any collections installed for size)"; + description = '' + The Ansible package to use. The default package is optimized for size, by not including the gazillion collections that pkgs.ansible and pkgs.ansible-core include. + ''; + example = literalExpression "pkgs.ansible"; }; collections = mkOption { type = types.attrsOf collectionType; default = {}; - description = "Collections to fetch and install"; + description = '' + Ansible collections to fetch and install from Galaxy. + ''; + example = { + "community-general" = { + version = "8.0.0"; + hash = "sha256-..."; + }; + }; }; dependencies = mkOption { type = types.listOf types.package; default = []; description = "List of packages to include at runtime"; + example = literalExpression "[pkgs.git pkgs.rsync]"; }; playbook = mkOption { type = playbookType; apply = res: filterUnset res; description = "The actual playbook, defined as a Nix data structure"; + example = [ + { + name = "Configure servers"; + hosts = "webservers"; + become = true; + tasks = [ + { + name = "Install nginx"; + package = { + name = "nginx"; + state = "present"; + }; + } + ]; + } + ]; }; inventory = mkOption { type = types.attrs; default = {}; - description = "Ansible inventory, will be converted to json and passed to ansible"; + description = '' + Ansible inventory, will be converted to JSON and passed to Ansible. + ''; + example = { + webservers = { + hosts = { + web1 = {ansible_host = "192.168.1.10";}; + }; + vars = { + http_port = 80; + }; + }; + }; }; inventoryFile = mkOption { From 76c6d3910049c6430bf02e065b6ca5896e5899f0 Mon Sep 17 00:00:00 2001 From: technofab Date: Thu, 25 Sep 2025 20:35:37 +0200 Subject: [PATCH 3/3] docs: update nixmkdocs, use svg logo, add style.css, set site_url --- docs/images/favicon.png | Bin 1949 -> 0 bytes docs/images/logo.png | Bin 16252 -> 0 bytes docs/images/logo.svg | 1 + docs/style.css | 15 +++++++ flake.lock | 7 ++-- flake.nix | 89 +++++++++++----------------------------- 6 files changed, 42 insertions(+), 70 deletions(-) delete mode 100644 docs/images/favicon.png delete mode 100644 docs/images/logo.png create mode 100644 docs/images/logo.svg create mode 100644 docs/style.css diff --git a/docs/images/favicon.png b/docs/images/favicon.png deleted file mode 100644 index 4b380b107f726c3dc530ae70f44bb78fccfc16ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1949 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 Ee~vN_8~^|S diff --git a/docs/images/logo.svg b/docs/images/logo.svg new file mode 100644 index 0000000..efba622 --- /dev/null +++ b/docs/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/style.css b/docs/style.css new file mode 100644 index 0000000..b2ae4ff --- /dev/null +++ b/docs/style.css @@ -0,0 +1,15 @@ +.md-header__button.md-logo { + margin: 0; + padding-top: .2rem; + padding-bottom: .2rem; +} + +[dir="ltr"] .md-header__title { + margin-left: 0; +} + +.md-header__button.md-logo img, +.md-header__button.md-logo svg { + height: 2rem; +} + diff --git a/flake.lock b/flake.lock index 2e3eb9a..7f0a07d 100644 --- a/flake.lock +++ b/flake.lock @@ -230,17 +230,16 @@ "nix-mkdocs": { "locked": { "dir": "lib", - "lastModified": 1754753501, - "narHash": "sha256-k7KEGQ7qejmwrSXVSvZ9zVD1H+ZtNm0I5fmCVmZVi+4=", + "lastModified": 1757055638, + "narHash": "sha256-KHYSkEreFe4meXzSdEbknC/HwaQSNClQkc8vzHlAsMM=", "owner": "technofab", "repo": "nixmkdocs", - "rev": "aba4f26f320b15043101824b65e72058ecab296f", + "rev": "7840a5febdbeaf2da90babf6c94b3d0929d2bf74", "type": "gitlab" }, "original": { "dir": "lib", "owner": "technofab", - "ref": "v1.0.0", "repo": "nixmkdocs", "type": "gitlab" } diff --git a/flake.nix b/flake.nix index 1eca9f5..dc9e417 100644 --- a/flake.nix +++ b/flake.nix @@ -39,56 +39,36 @@ convco.enable = true; }; }; - doc = { + docs."default".config = { path = ./docs; - deps = pp: [ - pp.mkdocs-material - pp.mkdocs-macros - (pp.callPackage inputs.mkdocs-material-umami {}) - ]; + material = { + enable = true; + colors = { + primary = "black"; + accent = "blue"; + }; + umami = { + enable = true; + src = "https://analytics.tf/umami"; + siteId = "d8354dfa-2ad2-4089-90d2-899b981aef22"; + domains = ["nixible.projects.tf"]; + }; + }; + macros = { + enable = true; + includeDir = toString self'.packages.optionsDocs; + }; config = { site_name = "Nixible"; + site_url = "https://nixible.projects.tf"; repo_name = "TECHNOFAB/nixible"; repo_url = "https://gitlab.com/TECHNOFAB/nixible"; - edit_uri = "edit/main/docs/"; + extra_css = ["style.css"]; theme = { - name = "material"; - features = ["content.code.copy" "content.action.edit"]; icon.repo = "simple/gitlab"; - logo = "images/logo.png"; - favicon = "images/favicon.png"; - palette = [ - { - scheme = "default"; - media = "(prefers-color-scheme: light)"; - primary = "black"; - accent = "blue"; - toggle = { - icon = "material/brightness-7"; - name = "Switch to dark mode"; - }; - } - { - scheme = "slate"; - media = "(prefers-color-scheme: dark)"; - primary = "black"; - accent = "blue"; - toggle = { - icon = "material/brightness-4"; - name = "Switch to light mode"; - }; - } - ]; + logo = "images/logo.svg"; + favicon = "images/logo.svg"; }; - plugins = [ - "search" - "material-umami" - { - macros = { - include_dir = self'.packages.optionsDocs; - }; - } - ]; nav = [ {"Introduction" = "index.md";} {"Usage" = "usage.md";} @@ -100,29 +80,6 @@ "pymdownx.superfences" "admonition" ]; - extra.analytics = { - provider = "umami"; - site_id = "d8354dfa-2ad2-4089-90d2-899b981aef22"; - src = "https://analytics.tf/umami"; - domains = "nixible.projects.tf"; - feedback = { - title = "Was this page helpful?"; - ratings = [ - { - icon = "material/thumb-up-outline"; - name = "This page is helpful"; - data = "good"; - note = "Thanks for your feedback!"; - } - { - icon = "material/thumb-down-outline"; - name = "This page could be improved"; - data = "bad"; - note = "Thanks for your feedback! Please leave feedback by creating an issue :)"; - } - ]; - }; - }; }; }; ci = { @@ -227,7 +184,7 @@ treefmt-nix.url = "github:numtide/treefmt-nix"; nix-gitlab-ci.url = "gitlab:technofab/nix-gitlab-ci?dir=lib"; nixtest.url = "gitlab:technofab/nixtest?dir=lib"; - nix-mkdocs.url = "gitlab:technofab/nixmkdocs/v1.0.0?dir=lib"; + nix-mkdocs.url = "gitlab:technofab/nixmkdocs?dir=lib"; mkdocs-material-umami.url = "gitlab:technofab/mkdocs-material-umami"; };