diff options
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/doc/manual/default.nix | 2 | ||||
-rw-r--r-- | nixos/doc/manual/release-notes/rl-2305.section.md | 8 | ||||
-rw-r--r-- | nixos/modules/i18n/input-method/fcitx5.nix | 38 | ||||
-rw-r--r-- | nixos/modules/services/matrix/dendrite.nix | 11 | ||||
-rw-r--r-- | nixos/modules/services/networking/bind.nix | 1 | ||||
-rw-r--r-- | nixos/modules/services/web-apps/mediawiki.nix | 56 | ||||
-rw-r--r-- | nixos/modules/services/web-apps/netbox.nix | 164 | ||||
-rw-r--r-- | nixos/tests/all-tests.nix | 3 | ||||
-rw-r--r-- | nixos/tests/elk.nix | 15 | ||||
-rw-r--r-- | nixos/tests/graylog.nix | 1 | ||||
-rw-r--r-- | nixos/tests/mediawiki.nix | 73 | ||||
-rw-r--r-- | nixos/tests/parsedmarc/default.nix | 4 | ||||
-rw-r--r-- | nixos/tests/web-apps/netbox.nix | 297 |
13 files changed, 546 insertions, 127 deletions
diff --git a/nixos/doc/manual/default.nix b/nixos/doc/manual/default.nix index 4032595e80598..68132f302e42d 100644 --- a/nixos/doc/manual/default.nix +++ b/nixos/doc/manual/default.nix @@ -168,7 +168,7 @@ let ./manual.md \ ./manual-combined-pre.xml - ${pkgs.libxslt.bin}/bin/xsltproc \ + xsltproc \ -o manual-combined.xml ${./../../lib/make-options-doc/postprocess-option-descriptions.xsl} \ manual-combined-pre.xml diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md index cbc58c6bdc5f1..28e4df793daf9 100644 --- a/nixos/doc/manual/release-notes/rl-2305.section.md +++ b/nixos/doc/manual/release-notes/rl-2305.section.md @@ -130,6 +130,8 @@ In addition to numerous new and upgraded packages, this release has the followin - The [services.unifi-video.openFirewall](#opt-services.unifi-video.openFirewall) module option default value has been changed from `true` to `false`. You will need to explicitly set this option to `true`, or configure your firewall. +- The option `i18n.inputMethod.fcitx5.enableRimeData` has been removed. Default RIME data is now included in `fcitx5-rime` by default, and can be customized using `fcitx5-rime.override { rimeDataPkgs = [ pkgs.rime-data, package2, ... ]; }` + - Kime has been updated from 2.5.6 to 3.0.2 and the `i18n.inputMethod.kime.config` option has been removed. Users should use `daemonModules`, `iconColor`, and `extraConfig` options under `i18n.inputMethod.kime` instead. - `tut` has been updated from 1.0.34 to 2.0.0, and now uses the TOML format for the configuration file instead of INI. Additional information can be found [here](https://github.com/RasmusLindroth/tut/releases/tag/2.0.0). @@ -154,6 +156,8 @@ In addition to numerous new and upgraded packages, this release has the followin - `lib.systems.examples.ghcjs` and consequently `pkgsCross.ghcjs` now use the target triplet `javascript-unknown-ghcjs` instead of `js-unknown-ghcjs`. This has been done to match an [upstream decision](https://gitlab.haskell.org/ghc/ghc/-/commit/6636b670233522f01d002c9b97827d00289dbf5c) to follow Cabal's platform naming more closely. Nixpkgs will also reject `js` as an architecture name. +- The old unsupported version 6.x of the ELK-stack and Elastic beats have been removed. Use OpenSearch instead. + - The `cosmoc` package has been removed. The upstream scripts in `cosmocc` should be used instead. - Qt 5.12 and 5.14 have been removed, as the corresponding branches have been EOL upstream for a long time. This affected under 10 packages in nixpkgs, largely unmaintained upstream as well, however, out-of-tree package expressions may need to be updated manually. @@ -231,6 +235,10 @@ In addition to numerous new and upgraded packages, this release has the followin - `services.openssh.ciphers` to `services.openssh.settings.Ciphers` - `services.openssh.gatewayPorts` to `services.openssh.settings.GatewayPorts` +- `netbox` was updated to 3.4. NixOS' `services.netbox.package` still defaults to 3.3 if `stateVersion` is earlier than 23.05. Please review upstream's [breaking changes](https://github.com/netbox-community/netbox/releases/tag/v3.4.0), and upgrade NetBox by changing `services.netbox.package`. Database migrations will be run automatically. + +- `services.netbox` now support RFC42-style options, through `services.netbox.settings`. + - `services.mastodon` gained a tootctl wrapped named `mastodon-tootctl` similar to `nextcloud-occ` which can be executed from any user and switches to the configured mastodon user with sudo and sources the environment variables. - DocBook option documentation, which has been deprecated since 22.11, will now cause a warning when documentation is built. Out-of-tree modules should migrate to using CommonMark documentation as outlined in [](#sec-option-declarations) to silence this warning. diff --git a/nixos/modules/i18n/input-method/fcitx5.nix b/nixos/modules/i18n/input-method/fcitx5.nix index aa816c90a3de3..7251240d26ac3 100644 --- a/nixos/modules/i18n/input-method/fcitx5.nix +++ b/nixos/modules/i18n/input-method/fcitx5.nix @@ -5,10 +5,9 @@ with lib; let im = config.i18n.inputMethod; cfg = im.fcitx5; - addons = cfg.addons ++ optional cfg.enableRimeData pkgs.rime-data; - fcitx5Package = pkgs.fcitx5-with-addons.override { inherit addons; }; - whetherRimeDataDir = any (p: p.pname == "fcitx5-rime") cfg.addons; -in { + fcitx5Package = pkgs.fcitx5-with-addons.override { inherit (cfg) addons; }; +in +{ options = { i18n.inputMethod.fcitx5 = { addons = mkOption { @@ -19,30 +18,23 @@ in { Enabled Fcitx5 addons. ''; }; - - enableRimeData = mkEnableOption (lib.mdDoc "default rime-data with fcitx5-rime"); }; }; + imports = [ + (mkRemovedOptionModule [ "i18n" "inputMethod" "fcitx5" "enableRimeData" ] '' + RIME data is now included in `fcitx5-rime` by default, and can be customized using `fcitx5-rime.override { rimeDataPkgs = ...; }` + '') + ]; + config = mkIf (im.enabled == "fcitx5") { i18n.inputMethod.package = fcitx5Package; - environment = mkMerge [{ - variables = { - GTK_IM_MODULE = "fcitx"; - QT_IM_MODULE = "fcitx"; - XMODIFIERS = "@im=fcitx"; - QT_PLUGIN_PATH = [ "${fcitx5Package}/${pkgs.qt6.qtbase.qtPluginPrefix}" ]; - }; - } - (mkIf whetherRimeDataDir { - pathsToLink = [ - "/share/rime-data" - ]; - - variables = { - NIX_RIME_DATA_DIR = "/run/current-system/sw/share/rime-data"; - }; - })]; + environment.variables = { + GTK_IM_MODULE = "fcitx"; + QT_IM_MODULE = "fcitx"; + XMODIFIERS = "@im=fcitx"; + QT_PLUGIN_PATH = [ "${fcitx5Package}/${pkgs.qt6.qtbase.qtPluginPrefix}" ]; + }; }; } diff --git a/nixos/modules/services/matrix/dendrite.nix b/nixos/modules/services/matrix/dendrite.nix index a8006547fc6b4..244c15fbf7a96 100644 --- a/nixos/modules/services/matrix/dendrite.nix +++ b/nixos/modules/services/matrix/dendrite.nix @@ -159,6 +159,15 @@ in ''; }; }; + options.relay_api.database = { + connection_string = lib.mkOption { + type = lib.types.str; + default = "file:relayapi.db"; + description = lib.mdDoc '' + Database for the Relay Server. + ''; + }; + }; options.media_api = { database = { connection_string = lib.mkOption { @@ -294,7 +303,7 @@ in -o /run/dendrite/dendrite.yaml '']; ExecStart = lib.strings.concatStringsSep " " ([ - "${pkgs.dendrite}/bin/dendrite-monolith-server" + "${pkgs.dendrite}/bin/dendrite" "--config /run/dendrite/dendrite.yaml" ] ++ lib.optionals (cfg.httpPort != null) [ "--http-bind-address :${builtins.toString cfg.httpPort}" diff --git a/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix index 05e8632e3125c..f963e341546c7 100644 --- a/nixos/modules/services/networking/bind.nix +++ b/nixos/modules/services/networking/bind.nix @@ -87,6 +87,7 @@ let }; '' } + allow-query { any; }; ${extraConfig} }; '') diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix index 07f2967486299..357c2d4a12830 100644 --- a/nixos/modules/services/web-apps/mediawiki.nix +++ b/nixos/modules/services/web-apps/mediawiki.nix @@ -46,6 +46,15 @@ let done ''; + dbAddr = if cfg.database.socket == null then + "${cfg.database.host}:${toString cfg.database.port}" + else if cfg.database.type == "mysql" then + "${cfg.database.host}:${cfg.database.socket}" + else if cfg.database.type == "postgres" then + "${cfg.database.socket}" + else + throw "Unsupported database type: ${cfg.database.type} for socket: ${cfg.database.socket}"; + mediawikiConfig = pkgs.writeText "LocalSettings.php" '' <?php # Protect against web entry @@ -87,7 +96,8 @@ let ## Database settings $wgDBtype = "${cfg.database.type}"; - $wgDBserver = "${cfg.database.host}:${if cfg.database.socket != null then cfg.database.socket else toString cfg.database.port}"; + $wgDBserver = "${dbAddr}"; + $wgDBport = "${toString cfg.database.port}"; $wgDBname = "${cfg.database.name}"; $wgDBuser = "${cfg.database.user}"; ${optionalString (cfg.database.passwordFile != null) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"} @@ -246,7 +256,8 @@ in port = mkOption { type = types.port; - default = 3306; + default = if cfg.database.type == "mysql" then 3306 else 5432; + defaultText = literalExpression "3306"; description = lib.mdDoc "Database host port."; }; @@ -286,14 +297,19 @@ in socket = mkOption { type = types.nullOr types.path; - default = if cfg.database.createLocally then "/run/mysqld/mysqld.sock" else null; + default = if (cfg.database.type == "mysql" && cfg.database.createLocally) then + "/run/mysqld/mysqld.sock" + else if (cfg.database.type == "postgres" && cfg.database.createLocally) then + "/run/postgresql" + else + null; defaultText = literalExpression "/run/mysqld/mysqld.sock"; description = lib.mdDoc "Path to the unix socket file to use for authentication."; }; createLocally = mkOption { type = types.bool; - default = cfg.database.type == "mysql"; + default = cfg.database.type == "mysql" || cfg.database.type == "postgres"; defaultText = literalExpression "true"; description = lib.mdDoc '' Create the database and database user locally. @@ -354,8 +370,8 @@ in config = mkIf cfg.enable { assertions = [ - { assertion = cfg.database.createLocally -> cfg.database.type == "mysql"; - message = "services.mediawiki.createLocally is currently only supported for database type 'mysql'"; + { assertion = cfg.database.createLocally -> (cfg.database.type == "mysql" || cfg.database.type == "postgres"); + message = "services.mediawiki.createLocally is currently only supported for database type 'mysql' and 'postgres'"; } { assertion = cfg.database.createLocally -> cfg.database.user == user; message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true"; @@ -374,15 +390,23 @@ in Vector = "${cfg.package}/share/mediawiki/skins/Vector"; }; - services.mysql = mkIf cfg.database.createLocally { + services.mysql = mkIf (cfg.database.type == "mysql" && cfg.database.createLocally) { enable = true; package = mkDefault pkgs.mariadb; ensureDatabases = [ cfg.database.name ]; - ensureUsers = [ - { name = cfg.database.user; - ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; - } - ]; + ensureUsers = [{ + name = cfg.database.user; + ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; + }]; + }; + + services.postgresql = mkIf (cfg.database.type == "postgres" && cfg.database.createLocally) { + enable = true; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [{ + name = cfg.database.user; + ensurePermissions = { "DATABASE \"${cfg.database.name}\"" = "ALL PRIVILEGES"; }; + }]; }; services.phpfpm.pools.mediawiki = { @@ -431,7 +455,8 @@ in systemd.services.mediawiki-init = { wantedBy = [ "multi-user.target" ]; before = [ "phpfpm-mediawiki.service" ]; - after = optional cfg.database.createLocally "mysql.service"; + after = optional (cfg.database.type == "mysql" && cfg.database.createLocally) "mysql.service" + ++ optional (cfg.database.type == "postgres" && cfg.database.createLocally) "postgresql.service"; script = '' if ! test -e "${stateDir}/secret.key"; then tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c 64 > ${stateDir}/secret.key @@ -442,7 +467,7 @@ in ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/install.php \ --confpath /tmp \ --scriptpath / \ - --dbserver ${cfg.database.host}${optionalString (cfg.database.socket != null) ":${cfg.database.socket}"} \ + --dbserver "${dbAddr}" \ --dbport ${toString cfg.database.port} \ --dbname ${cfg.database.name} \ ${optionalString (cfg.database.tablePrefix != null) "--dbprefix ${cfg.database.tablePrefix}"} \ @@ -464,7 +489,8 @@ in }; }; - systemd.services.httpd.after = optional (cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service"; + systemd.services.httpd.after = optional (cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service" + ++ optional (cfg.database.createLocally && cfg.database.type == "postgres") "postgresql.service"; users.users.${user} = { group = group; diff --git a/nixos/modules/services/web-apps/netbox.nix b/nixos/modules/services/web-apps/netbox.nix index e028f16004efe..0ecb20e8c2c0d 100644 --- a/nixos/modules/services/web-apps/netbox.nix +++ b/nixos/modules/services/web-apps/netbox.nix @@ -4,45 +4,17 @@ with lib; let cfg = config.services.netbox; + pythonFmt = pkgs.formats.pythonVars {}; staticDir = cfg.dataDir + "/static"; - configFile = pkgs.writeTextFile { - name = "configuration.py"; - text = '' - STATIC_ROOT = '${staticDir}' - MEDIA_ROOT = '${cfg.dataDir}/media' - REPORTS_ROOT = '${cfg.dataDir}/reports' - SCRIPTS_ROOT = '${cfg.dataDir}/scripts' - - ALLOWED_HOSTS = ['*'] - DATABASE = { - 'NAME': 'netbox', - 'USER': 'netbox', - 'HOST': '/run/postgresql', - } - - # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate - # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended - # to use two separate database IDs. - REDIS = { - 'tasks': { - 'URL': 'unix://${config.services.redis.servers.netbox.unixSocket}?db=0', - 'SSL': False, - }, - 'caching': { - 'URL': 'unix://${config.services.redis.servers.netbox.unixSocket}?db=1', - 'SSL': False, - } - } - - with open("${cfg.secretKeyFile}", "r") as file: - SECRET_KEY = file.readline() - - ${optionalString cfg.enableLdap "REMOTE_AUTH_BACKEND = 'netbox.authentication.LDAPBackend'"} - - ${cfg.extraConfig} - ''; + + settingsFile = pythonFmt.generate "netbox-settings.py" cfg.settings; + extraConfigFile = pkgs.writeTextFile { + name = "netbox-extraConfig.py"; + text = cfg.extraConfig; }; - pkg = (pkgs.netbox.overrideAttrs (old: { + configFile = pkgs.concatText "configuration.py" [ settingsFile extraConfigFile ]; + + pkg = (cfg.package.overrideAttrs (old: { installPhase = old.installPhase + '' ln -s ${configFile} $out/opt/netbox/netbox/netbox/configuration.py '' + optionalString cfg.enableLdap '' @@ -70,6 +42,30 @@ in { ''; }; + settings = lib.mkOption { + description = lib.mdDoc '' + Configuration options to set in `configuration.py`. + See the [documentation](https://docs.netbox.dev/en/stable/configuration/) for more possible options. + ''; + + default = { }; + + type = lib.types.submodule { + freeformType = pythonFmt.type; + + options = { + ALLOWED_HOSTS = lib.mkOption { + type = with lib.types; listOf str; + default = ["*"]; + description = lib.mdDoc '' + A list of valid fully-qualified domain names (FQDNs) and/or IP + addresses that can be used to reach the NetBox service. + ''; + }; + }; + }; + }; + listenAddress = mkOption { type = types.str; default = "[::1]"; @@ -78,6 +74,17 @@ in { ''; }; + package = mkOption { + type = types.package; + default = if versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3; + defaultText = literalExpression '' + if versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3; + ''; + description = lib.mdDoc '' + NetBox package to use. + ''; + }; + port = mkOption { type = types.port; default = 8001; @@ -117,7 +124,7 @@ in { default = ""; description = lib.mdDoc '' Additional lines of configuration appended to the `configuration.py`. - See the [documentation](https://netbox.readthedocs.io/en/stable/configuration/optional-settings/) for more possible options. + See the [documentation](https://docs.netbox.dev/en/stable/configuration/) for more possible options. ''; }; @@ -138,11 +145,90 @@ in { Path to the Configuration-File for LDAP-Authentication, will be loaded as `ldap_config.py`. See the [documentation](https://netbox.readthedocs.io/en/stable/installation/6-ldap/#configuration) for possible options. ''; + example = '' + import ldap + from django_auth_ldap.config import LDAPSearch, PosixGroupType + + AUTH_LDAP_SERVER_URI = "ldaps://ldap.example.com/" + + AUTH_LDAP_USER_SEARCH = LDAPSearch( + "ou=accounts,ou=posix,dc=example,dc=com", + ldap.SCOPE_SUBTREE, + "(uid=%(user)s)", + ) + + AUTH_LDAP_GROUP_SEARCH = LDAPSearch( + "ou=groups,ou=posix,dc=example,dc=com", + ldap.SCOPE_SUBTREE, + "(objectClass=posixGroup)", + ) + AUTH_LDAP_GROUP_TYPE = PosixGroupType() + + # Mirror LDAP group assignments. + AUTH_LDAP_MIRROR_GROUPS = True + + # For more granular permissions, we can map LDAP groups to Django groups. + AUTH_LDAP_FIND_GROUP_PERMS = True + ''; }; }; config = mkIf cfg.enable { - services.netbox.plugins = mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]); + services.netbox = { + plugins = mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]); + settings = { + STATIC_ROOT = staticDir; + MEDIA_ROOT = "${cfg.dataDir}/media"; + REPORTS_ROOT = "${cfg.dataDir}/reports"; + SCRIPTS_ROOT = "${cfg.dataDir}/scripts"; + + DATABASE = { + NAME = "netbox"; + USER = "netbox"; + HOST = "/run/postgresql"; + }; + + # Redis database settings. Redis is used for caching and for queuing + # background tasks such as webhook events. A separate configuration + # exists for each. Full connection details are required in both + # sections, and it is strongly recommended to use two separate database + # IDs. + REDIS = { + tasks = { + URL = "unix://${config.services.redis.servers.netbox.unixSocket}?db=0"; + SSL = false; + }; + caching = { + URL = "unix://${config.services.redis.servers.netbox.unixSocket}?db=1"; + SSL = false; + }; + }; + + REMOTE_AUTH_BACKEND = lib.mkIf cfg.enableLdap "netbox.authentication.LDAPBackend"; + + LOGGING = lib.mkDefault { + version = 1; + + formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s"; + + handlers.console = { + class = "logging.StreamHandler"; + formatter = "precise"; + }; + + # log to console/systemd instead of file + root = { + level = "INFO"; + handlers = [ "console" ]; + }; + }; + }; + + extraConfig = '' + with open("${cfg.secretKeyFile}", "r") as file: + SECRET_KEY = file.readline() + ''; + }; services.redis.servers.netbox.enable = true; diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index a155510450b12..0783f3bf68e25 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -460,7 +460,8 @@ in { netdata = handleTest ./netdata.nix {}; networking.networkd = handleTest ./networking.nix { networkd = true; }; networking.scripted = handleTest ./networking.nix { networkd = false; }; - netbox = handleTest ./web-apps/netbox.nix {}; + netbox = handleTest ./web-apps/netbox.nix { inherit (pkgs) netbox; }; + netbox_3_3 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_3_3; }; # TODO: put in networking.nix after the test becomes more complete networkingProxy = handleTest ./networking-proxy.nix {}; nextcloud = handleTest ./nextcloud {}; diff --git a/nixos/tests/elk.nix b/nixos/tests/elk.nix index f42be00f23b82..5c332cb5f2eea 100644 --- a/nixos/tests/elk.nix +++ b/nixos/tests/elk.nix @@ -268,14 +268,6 @@ let ''; }) { inherit pkgs system; }; in { - ELK-6 = mkElkTest "elk-6-oss" { - name = "elk-6-oss"; - elasticsearch = pkgs.elasticsearch6-oss; - logstash = pkgs.logstash6-oss; - kibana = pkgs.kibana6-oss; - journalbeat = pkgs.journalbeat6; - metricbeat = pkgs.metricbeat6; - }; # We currently only package upstream binaries. # Feel free to package an SSPL licensed source-based package! # ELK-7 = mkElkTest "elk-7-oss" { @@ -287,13 +279,6 @@ in { # metricbeat = pkgs.metricbeat7; # }; unfree = lib.dontRecurseIntoAttrs { - ELK-6 = mkElkTest "elk-6" { - elasticsearch = pkgs.elasticsearch6; - logstash = pkgs.logstash6; - kibana = pkgs.kibana6; - journalbeat = pkgs.journalbeat6; - metricbeat = pkgs.metricbeat6; - }; ELK-7 = mkElkTest "elk-7" { elasticsearch = pkgs.elasticsearch7; logstash = pkgs.logstash7; diff --git a/nixos/tests/graylog.nix b/nixos/tests/graylog.nix index 23f426fc7af95..3f7cc3a914390 100644 --- a/nixos/tests/graylog.nix +++ b/nixos/tests/graylog.nix @@ -8,7 +8,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { services.mongodb.enable = true; services.elasticsearch.enable = true; - services.elasticsearch.package = pkgs.elasticsearch-oss; services.elasticsearch.extraConf = '' network.publish_host: 127.0.0.1 network.bind_host: 127.0.0.1 diff --git a/nixos/tests/mediawiki.nix b/nixos/tests/mediawiki.nix index 7f31d6aadfa2c..1ae82d65b3cb9 100644 --- a/nixos/tests/mediawiki.nix +++ b/nixos/tests/mediawiki.nix @@ -1,28 +1,57 @@ -import ./make-test-python.nix ({ pkgs, lib, ... }: { - name = "mediawiki"; - meta.maintainers = [ lib.maintainers.aanderse ]; +{ + system ? builtins.currentSystem, + config ? {}, + pkgs ? import ../.. { inherit system config; }, +}: - nodes.machine = - { ... }: - { services.mediawiki.enable = true; - services.mediawiki.virtualHost.hostName = "localhost"; - services.mediawiki.virtualHost.adminAddr = "root@example.com"; - services.mediawiki.passwordFile = pkgs.writeText "password" "correcthorsebatterystaple"; - services.mediawiki.extensions = { - Matomo = pkgs.fetchzip { - url = "https://github.com/DaSchTour/matomo-mediawiki-extension/archive/v4.0.1.tar.gz"; - sha256 = "0g5rd3zp0avwlmqagc59cg9bbkn3r7wx7p6yr80s644mj6dlvs1b"; - }; - ParserFunctions = null; +let + shared = { + services.mediawiki.enable = true; + services.mediawiki.virtualHost.hostName = "localhost"; + services.mediawiki.virtualHost.adminAddr = "root@example.com"; + services.mediawiki.passwordFile = pkgs.writeText "password" "correcthorsebatterystaple"; + services.mediawiki.extensions = { + Matomo = pkgs.fetchzip { + url = "https://github.com/DaSchTour/matomo-mediawiki-extension/archive/v4.0.1.tar.gz"; + sha256 = "0g5rd3zp0avwlmqagc59cg9bbkn3r7wx7p6yr80s644mj6dlvs1b"; }; + ParserFunctions = null; }; + }; - testScript = '' - start_all() + testLib = import ../lib/testing-python.nix { + inherit system pkgs; + extraConfigurations = [ shared ]; + }; +in +{ + mysql = testLib.makeTest { + name = "mediawiki-mysql"; + nodes.machine = { + services.mediawiki.database.type = "mysql"; + }; + testScript = '' + start_all() + + machine.wait_for_unit("phpfpm-mediawiki.service") + + page = machine.succeed("curl -fL http://localhost/") + assert "MediaWiki has been installed" in page + ''; + }; + + postgresql = testLib.makeTest { + name = "mediawiki-postgres"; + nodes.machine = { + services.mediawiki.database.type = "postgres"; + }; + testScript = '' + start_all() - machine.wait_for_unit("phpfpm-mediawiki.service") + machine.wait_for_unit("phpfpm-mediawiki.service") - page = machine.succeed("curl -fL http://localhost/") - assert "MediaWiki has been installed" in page - ''; -}) + page = machine.succeed("curl -fL http://localhost/") + assert "MediaWiki has been installed" in page + ''; + }; +} diff --git a/nixos/tests/parsedmarc/default.nix b/nixos/tests/parsedmarc/default.nix index 837cf9d7e6dce..1feadcb7f39b0 100644 --- a/nixos/tests/parsedmarc/default.nix +++ b/nixos/tests/parsedmarc/default.nix @@ -84,8 +84,6 @@ in }; }; - services.elasticsearch.package = pkgs.elasticsearch-oss; - environment.systemPackages = [ (sendEmail "dmarc@localhost") pkgs.jq @@ -158,8 +156,6 @@ in }; }; - services.elasticsearch.package = pkgs.elasticsearch-oss; - environment.systemPackages = [ pkgs.jq ]; diff --git a/nixos/tests/web-apps/netbox.nix b/nixos/tests/web-apps/netbox.nix index 35decdd49e870..30de74f1886c0 100644 --- a/nixos/tests/web-apps/netbox.nix +++ b/nixos/tests/web-apps/netbox.nix @@ -1,21 +1,146 @@ -import ../make-test-python.nix ({ lib, pkgs, ... }: { +let + ldapDomain = "example.org"; + ldapSuffix = "dc=example,dc=org"; + + ldapRootUser = "admin"; + ldapRootPassword = "foobar"; + + testUser = "alice"; + testPassword = "verySecure"; + testGroup = "netbox-users"; +in import ../make-test-python.nix ({ lib, pkgs, netbox, ... }: { name = "netbox"; meta = with lib.maintainers; { - maintainers = [ n0emis ]; + maintainers = [ minijackson n0emis ]; }; - nodes.machine = { ... }: { + nodes.machine = { config, ... }: { services.netbox = { enable = true; + package = netbox; secretKeyFile = pkgs.writeText "secret" '' abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ''; + + enableLdap = true; + ldapConfigPath = pkgs.writeText "ldap_config.py" '' + import ldap + from django_auth_ldap.config import LDAPSearch, PosixGroupType + + AUTH_LDAP_SERVER_URI = "ldap://localhost/" + + AUTH_LDAP_USER_SEARCH = LDAPSearch( + "ou=accounts,ou=posix,${ldapSuffix}", + ldap.SCOPE_SUBTREE, + "(uid=%(user)s)", + ) + + AUTH_LDAP_GROUP_SEARCH = LDAPSearch( + "ou=groups,ou=posix,${ldapSuffix}", + ldap.SCOPE_SUBTREE, + "(objectClass=posixGroup)", + ) + AUTH_LDAP_GROUP_TYPE = PosixGroupType() + + # Mirror LDAP group assignments. + AUTH_LDAP_MIRROR_GROUPS = True + + # For more granular permissions, we can map LDAP groups to Django groups. + AUTH_LDAP_FIND_GROUP_PERMS = True + ''; + }; + + services.nginx = { + enable = true; + + recommendedProxySettings = true; + + virtualHosts.netbox = { + default = true; + locations."/".proxyPass = "http://localhost:${toString config.services.netbox.port}"; + locations."/static/".alias = "/var/lib/netbox/static/"; + }; + }; + + # Adapted from the sssd-ldap NixOS test + services.openldap = { + enable = true; + settings = { + children = { + "cn=schema".includes = [ + "${pkgs.openldap}/etc/schema/core.ldif" + "${pkgs.openldap}/etc/schema/cosine.ldif" + "${pkgs.openldap}/etc/schema/inetorgperson.ldif" + "${pkgs.openldap}/etc/schema/nis.ldif" + ]; + "olcDatabase={1}mdb" = { + attrs = { + objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ]; + olcDatabase = "{1}mdb"; + olcDbDirectory = "/var/lib/openldap/db"; + olcSuffix = ldapSuffix; + olcRootDN = "cn=${ldapRootUser},${ldapSuffix}"; + olcRootPW = ldapRootPassword; + }; + }; + }; + }; + declarativeContents = { + ${ldapSuffix} = '' + dn: ${ldapSuffix} + objectClass: top + objectClass: dcObject + objectClass: organization + o: ${ldapDomain} + + dn: ou=posix,${ldapSuffix} + objectClass: top + objectClass: organizationalUnit + + dn: ou=accounts,ou=posix,${ldapSuffix} + objectClass: top + objectClass: organizationalUnit + + dn: uid=${testUser},ou=accounts,ou=posix,${ldapSuffix} + objectClass: person + objectClass: posixAccount + userPassword: ${testPassword} + homeDirectory: /home/${testUser} + uidNumber: 1234 + gidNumber: 1234 + cn: "" + sn: "" + + dn: ou=groups,ou=posix,${ldapSuffix} + objectClass: top + objectClass: organizationalUnit + + dn: cn=${testGroup},ou=groups,ou=posix,${ldapSuffix} + objectClass: posixGroup + gidNumber: 2345 + memberUid: ${testUser} + ''; + }; }; + + users.users.nginx.extraGroups = [ "netbox" ]; + + networking.firewall.allowedTCPPorts = [ 80 ]; }; - testScript = '' - machine.start() + testScript = let + changePassword = pkgs.writeText "change-password.py" '' + from django.contrib.auth.models import User + u = User.objects.get(username='netbox') + u.set_password('netbox') + u.save() + ''; + in '' + from typing import Any, Dict + import json + + start_all() machine.wait_for_unit("netbox.target") machine.wait_until_succeeds("journalctl --since -1m --unit netbox --grep Listening") @@ -26,5 +151,167 @@ import ../make-test-python.nix ({ lib, pkgs, ... }: { with subtest("Staticfiles are generated"): machine.succeed("test -e /var/lib/netbox/static/netbox.js") + + with subtest("Superuser can be created"): + machine.succeed( + "netbox-manage createsuperuser --noinput --username netbox --email netbox@example.com" + ) + # Django doesn't have a "clean" way of inputting the password from the command line + machine.succeed("cat '${changePassword}' | netbox-manage shell") + + machine.wait_for_unit("network.target") + + with subtest("Home screen loads from nginx"): + machine.succeed( + "curl -sSfL http://localhost | grep '<title>Home | NetBox</title>'" + ) + + with subtest("Staticfiles can be fetched"): + machine.succeed("curl -sSfL http://localhost/static/netbox.js") + machine.succeed("curl -sSfL http://localhost/static/docs/") + + with subtest("Can interact with API"): + json.loads( + machine.succeed("curl -sSfL -H 'Accept: application/json' 'http://localhost/api/'") + ) + + def login(username: str, password: str): + encoded_data = json.dumps({"username": username, "password": password}) + uri = "/users/tokens/provision/" + result = json.loads( + machine.succeed( + "curl -sSfL " + "-X POST " + "-H 'Accept: application/json' " + "-H 'Content-Type: application/json' " + f"'http://localhost/api{uri}' " + f"--data '{encoded_data}'" + ) + ) + return result["key"] + + with subtest("Can login"): + auth_token = login("netbox", "netbox") + + def get(uri: str): + return json.loads( + machine.succeed( + "curl -sSfL " + "-H 'Accept: application/json' " + f"-H 'Authorization: Token {auth_token}' " + f"'http://localhost/api{uri}'" + ) + ) + + def delete(uri: str): + return machine.succeed( + "curl -sSfL " + f"-X DELETE " + "-H 'Accept: application/json' " + f"-H 'Authorization: Token {auth_token}' " + f"'http://localhost/api{uri}'" + ) + + + def data_request(uri: str, method: str, data: Dict[str, Any]): + encoded_data = json.dumps(data) + return json.loads( + machine.succeed( + "curl -sSfL " + f"-X {method} " + "-H 'Accept: application/json' " + "-H 'Content-Type: application/json' " + f"-H 'Authorization: Token {auth_token}' " + f"'http://localhost/api{uri}' " + f"--data '{encoded_data}'" + ) + ) + + def post(uri: str, data: Dict[str, Any]): + return data_request(uri, "POST", data) + + def patch(uri: str, data: Dict[str, Any]): + return data_request(uri, "PATCH", data) + + with subtest("Can create objects"): + result = post("/dcim/sites/", {"name": "Test site", "slug": "test-site"}) + site_id = result["id"] + + # Example from: + # http://netbox.extra.cea.fr/static/docs/integrations/rest-api/#creating-a-new-object + post("/ipam/prefixes/", {"prefix": "192.0.2.0/24", "site": site_id}) + + result = post( + "/dcim/manufacturers/", + {"name": "Test manufacturer", "slug": "test-manufacturer"} + ) + manufacturer_id = result["id"] + + # Had an issue with device-types before NetBox 3.4.0 + result = post( + "/dcim/device-types/", + { + "model": "Test device type", + "manufacturer": manufacturer_id, + "slug": "test-device-type", + }, + ) + device_type_id = result["id"] + + with subtest("Can list objects"): + result = get("/dcim/sites/") + + assert result["count"] == 1 + assert result["results"][0]["id"] == site_id + assert result["results"][0]["name"] == "Test site" + assert result["results"][0]["description"] == "" + + result = get("/dcim/device-types/") + assert result["count"] == 1 + assert result["results"][0]["id"] == device_type_id + assert result["results"][0]["model"] == "Test device type" + + with subtest("Can update objects"): + new_description = "Test site description" + patch(f"/dcim/sites/{site_id}/", {"description": new_description}) + result = get(f"/dcim/sites/{site_id}/") + assert result["description"] == new_description + + with subtest("Can delete objects"): + # Delete a device-type since no object depends on it + delete(f"/dcim/device-types/{device_type_id}/") + + result = get("/dcim/device-types/") + assert result["count"] == 0 + + with subtest("Can use the GraphQL API"): + encoded_data = json.dumps({ + "query": "query { prefix_list { prefix, site { id, description } } }", + }) + result = json.loads( + machine.succeed( + "curl -sSfL " + "-H 'Accept: application/json' " + "-H 'Content-Type: application/json' " + f"-H 'Authorization: Token {auth_token}' " + "'http://localhost/graphql/' " + f"--data '{encoded_data}'" + ) + ) + + assert len(result["data"]["prefix_list"]) == 1 + assert result["data"]["prefix_list"][0]["prefix"] == "192.0.2.0/24" + assert result["data"]["prefix_list"][0]["site"]["id"] == str(site_id) + assert result["data"]["prefix_list"][0]["site"]["description"] == new_description + + with subtest("Can login with LDAP"): + machine.wait_for_unit("openldap.service") + login("alice", "${testPassword}") + + with subtest("Can associate LDAP groups"): + result = get("/users/users/?username=${testUser}") + + assert result["count"] == 1 + assert any(group["name"] == "${testGroup}" for group in result["results"][0]["groups"]) ''; }) |