From 0b757d681f4b9fa91187ebb03e353cc20d74e9d6 Mon Sep 17 00:00:00 2001 From: Aaron Jheng Date: Sat, 30 Sep 2023 14:28:17 +0000 Subject: cassandra: use cassandra_4 --- nixos/doc/manual/release-notes/rl-2311.section.md | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nixos/doc') diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md index dc8f989c686..6aa7fd18d06 100644 --- a/nixos/doc/manual/release-notes/rl-2311.section.md +++ b/nixos/doc/manual/release-notes/rl-2311.section.md @@ -38,6 +38,8 @@ true`. This is generally safe behavior, but for anyone needing to opt out from the check `users.users.${USERNAME}.ignoreShellProgramCheck = true` will do the job. +- Cassandra now defaults to 4.x, updated from 3.11.x. + ## New Services {#sec-release-23.11-new-services} - [MCHPRS](https://github.com/MCHPR/MCHPRS), a multithreaded Minecraft server built for redstone. Available as [services.mchprs](#opt-services.mchprs.enable). -- cgit 1.4.1 From 84722633b786ceb3aef8be3959b072c901b75dc9 Mon Sep 17 00:00:00 2001 From: nikstur Date: Tue, 31 Oct 2023 11:18:46 +0100 Subject: nixos/image: move docs into manual --- .../building-images-via-systemd-repart.chapter.md | 137 +++++++++++++++++++++ nixos/doc/manual/installation/installation.md | 1 + nixos/modules/image/repart.md | 137 --------------------- nixos/modules/image/repart.nix | 5 +- 4 files changed, 139 insertions(+), 141 deletions(-) create mode 100644 nixos/doc/manual/installation/building-images-via-systemd-repart.chapter.md delete mode 100644 nixos/modules/image/repart.md (limited to 'nixos/doc') diff --git a/nixos/doc/manual/installation/building-images-via-systemd-repart.chapter.md b/nixos/doc/manual/installation/building-images-via-systemd-repart.chapter.md new file mode 100644 index 00000000000..6d0675f21a0 --- /dev/null +++ b/nixos/doc/manual/installation/building-images-via-systemd-repart.chapter.md @@ -0,0 +1,137 @@ +# Building Images via `systemd-repart` {#sec-image-repart} + +You can build disk images in NixOS with the `image.repart` option provided by +the module [image/repart.nix][]. This module uses `systemd-repart` to build the +images and exposes it's entire interface via the `repartConfig` option. + +[image/repart.nix]: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/image/repart.nix + +An example of how to build an image: + +```nix +{ config, modulesPath, ... }: { + + imports = [ "${modulesPath}/image/repart.nix" ]; + + image.repart = { + name = "image"; + partitions = { + "esp" = { + contents = { + ... + }; + repartConfig = { + Type = "esp"; + ... + }; + }; + "root" = { + storePaths = [ config.system.build.toplevel ]; + repartConfig = { + Type = "root"; + Label = "nixos"; + ... + }; + }; + }; + }; + +} +``` + +## Nix Store Partition {#sec-image-repart-store-partition} + +You can define a partition that only contains the Nix store and then mount it +under `/nix/store`. Because the `/nix/store` part of the paths is already +determined by the mount point, you have to set `stripNixStorePrefix = true;` so +that the prefix is stripped from the paths before copying them into the image. + +```nix +fileSystems."/nix/store".device = "/dev/disk/by-partlabel/nix-store" + +image.repart.partitions = { + "store" = { + storePaths = [ config.system.build.toplevel ]; + stripNixStorePrefix = true; + repartConfig = { + Type = "linux-generic"; + Label = "nix-store"; + ... + }; + }; +}; +``` + +## Appliance Image {#sec-image-repart-appliance} + +The `image/repart.nix` module can also be used to build self-contained [software +appliances][]. + +[software appliances]: https://en.wikipedia.org/wiki/Software_appliance + +The generation based update mechanism of NixOS is not suited for appliances. +Updates of appliances are usually either performed by replacing the entire +image with a new one or by updating partitions via an A/B scheme. See the +[Chrome OS update process][chrome-os-update] for an example of how to achieve +this. The appliance image built in the following example does not contain a +`configuration.nix` and thus you will not be able to call `nixos-rebuild` from +this system. + +[chrome-os-update]: https://chromium.googlesource.com/aosp/platform/system/update_engine/+/HEAD/README.md + +```nix +let + pkgs = import { }; + efiArch = pkgs.stdenv.hostPlatform.efiArch; +in +(pkgs.nixos [ + ({ config, lib, pkgs, modulesPath, ... }: { + + imports = [ "${modulesPath}/image/repart.nix" ]; + + boot.loader.grub.enable = false; + + fileSystems."/".device = "/dev/disk/by-label/nixos"; + + image.repart = { + name = "image"; + partitions = { + "esp" = { + contents = { + "/EFI/BOOT/BOOT${lib.toUpper efiArch}.EFI".source = + "${pkgs.systemd}/lib/systemd/boot/efi/systemd-boot${efiArch}.efi"; + + "/loader/entries/nixos.conf".source = pkgs.writeText "nixos.conf" '' + title NixOS + linux /EFI/nixos/kernel.efi + initrd /EFI/nixos/initrd.efi + options init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} + ''; + + "/EFI/nixos/kernel.efi".source = + "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}"; + + "/EFI/nixos/initrd.efi".source = + "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}"; + }; + repartConfig = { + Type = "esp"; + Format = "vfat"; + SizeMinBytes = "96M"; + }; + }; + "root" = { + storePaths = [ config.system.build.toplevel ]; + repartConfig = { + Type = "root"; + Format = "ext4"; + Label = "nixos"; + Minimize = "guess"; + }; + }; + }; + }; + + }) +]).image +``` diff --git a/nixos/doc/manual/installation/installation.md b/nixos/doc/manual/installation/installation.md index 14059425660..f3b1773d865 100644 --- a/nixos/doc/manual/installation/installation.md +++ b/nixos/doc/manual/installation/installation.md @@ -8,4 +8,5 @@ installing.chapter.md changing-config.chapter.md upgrading.chapter.md building-nixos.chapter.md +building-images-via-systemd-repart.chapter.md ``` diff --git a/nixos/modules/image/repart.md b/nixos/modules/image/repart.md deleted file mode 100644 index 6d0675f21a0..00000000000 --- a/nixos/modules/image/repart.md +++ /dev/null @@ -1,137 +0,0 @@ -# Building Images via `systemd-repart` {#sec-image-repart} - -You can build disk images in NixOS with the `image.repart` option provided by -the module [image/repart.nix][]. This module uses `systemd-repart` to build the -images and exposes it's entire interface via the `repartConfig` option. - -[image/repart.nix]: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/image/repart.nix - -An example of how to build an image: - -```nix -{ config, modulesPath, ... }: { - - imports = [ "${modulesPath}/image/repart.nix" ]; - - image.repart = { - name = "image"; - partitions = { - "esp" = { - contents = { - ... - }; - repartConfig = { - Type = "esp"; - ... - }; - }; - "root" = { - storePaths = [ config.system.build.toplevel ]; - repartConfig = { - Type = "root"; - Label = "nixos"; - ... - }; - }; - }; - }; - -} -``` - -## Nix Store Partition {#sec-image-repart-store-partition} - -You can define a partition that only contains the Nix store and then mount it -under `/nix/store`. Because the `/nix/store` part of the paths is already -determined by the mount point, you have to set `stripNixStorePrefix = true;` so -that the prefix is stripped from the paths before copying them into the image. - -```nix -fileSystems."/nix/store".device = "/dev/disk/by-partlabel/nix-store" - -image.repart.partitions = { - "store" = { - storePaths = [ config.system.build.toplevel ]; - stripNixStorePrefix = true; - repartConfig = { - Type = "linux-generic"; - Label = "nix-store"; - ... - }; - }; -}; -``` - -## Appliance Image {#sec-image-repart-appliance} - -The `image/repart.nix` module can also be used to build self-contained [software -appliances][]. - -[software appliances]: https://en.wikipedia.org/wiki/Software_appliance - -The generation based update mechanism of NixOS is not suited for appliances. -Updates of appliances are usually either performed by replacing the entire -image with a new one or by updating partitions via an A/B scheme. See the -[Chrome OS update process][chrome-os-update] for an example of how to achieve -this. The appliance image built in the following example does not contain a -`configuration.nix` and thus you will not be able to call `nixos-rebuild` from -this system. - -[chrome-os-update]: https://chromium.googlesource.com/aosp/platform/system/update_engine/+/HEAD/README.md - -```nix -let - pkgs = import { }; - efiArch = pkgs.stdenv.hostPlatform.efiArch; -in -(pkgs.nixos [ - ({ config, lib, pkgs, modulesPath, ... }: { - - imports = [ "${modulesPath}/image/repart.nix" ]; - - boot.loader.grub.enable = false; - - fileSystems."/".device = "/dev/disk/by-label/nixos"; - - image.repart = { - name = "image"; - partitions = { - "esp" = { - contents = { - "/EFI/BOOT/BOOT${lib.toUpper efiArch}.EFI".source = - "${pkgs.systemd}/lib/systemd/boot/efi/systemd-boot${efiArch}.efi"; - - "/loader/entries/nixos.conf".source = pkgs.writeText "nixos.conf" '' - title NixOS - linux /EFI/nixos/kernel.efi - initrd /EFI/nixos/initrd.efi - options init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} - ''; - - "/EFI/nixos/kernel.efi".source = - "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}"; - - "/EFI/nixos/initrd.efi".source = - "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}"; - }; - repartConfig = { - Type = "esp"; - Format = "vfat"; - SizeMinBytes = "96M"; - }; - }; - "root" = { - storePaths = [ config.system.build.toplevel ]; - repartConfig = { - Type = "root"; - Format = "ext4"; - Label = "nixos"; - Minimize = "guess"; - }; - }; - }; - }; - - }) -]).image -``` diff --git a/nixos/modules/image/repart.nix b/nixos/modules/image/repart.nix index 926791d8570..e006ce83ce7 100644 --- a/nixos/modules/image/repart.nix +++ b/nixos/modules/image/repart.nix @@ -206,10 +206,7 @@ in | tee repart-output.json ''; - meta = { - maintainers = with lib.maintainers; [ nikstur ]; - doc = ./repart.md; - }; + meta.maintainers = with lib.maintainers; [ nikstur ]; }; } -- cgit 1.4.1 From 5927d556855d43f1d0495f3fb7ee2a287f04d8ce Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Tue, 31 Oct 2023 14:12:00 +0100 Subject: privacyidea: remove Related to #262907 (Django3 removal from nixpkgs). This package already required an unreasonable amount of maintenance regularly for a such small leaf-package. It has a few highly outdated dependencies (e.g. flask 1, jinja2 2.11, sqlalchemy 1.3). After at least each Python package-set update one had to fix up a lot of dependencies to fix the package itself, so it was only useful on stable branches. And having so much outdated software in a security-sensitive piece of software seems questionable. Finally, globin and I won't be available for maintaining this now that Mayflower is migrating to another solution (and we'll do that as well) and I'd expect this to bitrot extremely quick if we both bail out. --- nixos/doc/manual/release-notes/rl-2311.section.md | 2 + nixos/modules/module-list.nix | 1 - nixos/modules/services/security/privacyidea.nix | 458 --------------------- nixos/tests/all-tests.nix | 1 - nixos/tests/privacyidea.nix | 43 -- pkgs/applications/misc/privacyidea/default.nix | 263 ------------ .../privacyidea-ldap-proxy/default.nix | 32 -- pkgs/top-level/aliases.nix | 1 + pkgs/top-level/all-packages.nix | 2 - pkgs/top-level/python-aliases.nix | 2 +- pkgs/top-level/python-packages.nix | 2 - 11 files changed, 4 insertions(+), 803 deletions(-) delete mode 100644 nixos/modules/services/security/privacyidea.nix delete mode 100644 nixos/tests/privacyidea.nix delete mode 100644 pkgs/applications/misc/privacyidea/default.nix delete mode 100644 pkgs/development/python-modules/privacyidea-ldap-proxy/default.nix (limited to 'nixos/doc') diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md index 7af4a99906c..276e7c9384e 100644 --- a/nixos/doc/manual/release-notes/rl-2311.section.md +++ b/nixos/doc/manual/release-notes/rl-2311.section.md @@ -339,6 +339,8 @@ - `service.borgmatic.settings.location` and `services.borgmatic.configurations..location` are deprecated, please move your options out of sections to the global scope. +- `privacyidea` (and the corresponding `privacyidea-ldap-proxy`) has been removed from nixpkgs because it has severely outdated dependencies that became unmaintainable with nixpkgs' python package-set. + - `dagger` was removed because using a package called `dagger` and packaging it from source violates their trademark policy. - `win-virtio` package was renamed to `virtio-win` to be consistent with the upstream package name. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 4949eb6f298..5647e89e541 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1176,7 +1176,6 @@ ./services/security/opensnitch.nix ./services/security/pass-secret-service.nix ./services/security/physlock.nix - ./services/security/privacyidea.nix ./services/security/shibboleth-sp.nix ./services/security/sks.nix ./services/security/sshguard.nix diff --git a/nixos/modules/services/security/privacyidea.nix b/nixos/modules/services/security/privacyidea.nix deleted file mode 100644 index 664335cb58e..00000000000 --- a/nixos/modules/services/security/privacyidea.nix +++ /dev/null @@ -1,458 +0,0 @@ -{ config, lib, options, pkgs, ... }: - -with lib; - -let - cfg = config.services.privacyidea; - opt = options.services.privacyidea; - - uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; python3 = pkgs.python310; }; - python = uwsgi.python3; - penv = python.withPackages (const [ pkgs.privacyidea ]); - logCfg = pkgs.writeText "privacyidea-log.cfg" '' - [formatters] - keys=detail - - [handlers] - keys=stream - - [formatter_detail] - class=privacyidea.lib.log.SecureFormatter - format=[%(asctime)s][%(process)d][%(thread)d][%(levelname)s][%(name)s:%(lineno)d] %(message)s - - [handler_stream] - class=StreamHandler - level=NOTSET - formatter=detail - args=(sys.stdout,) - - [loggers] - keys=root,privacyidea - - [logger_privacyidea] - handlers=stream - qualname=privacyidea - level=INFO - - [logger_root] - handlers=stream - level=ERROR - ''; - - piCfgFile = pkgs.writeText "privacyidea.cfg" '' - SUPERUSER_REALM = [ '${concatStringsSep "', '" cfg.superuserRealm}' ] - SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2:///privacyidea' - SECRET_KEY = '${cfg.secretKey}' - PI_PEPPER = '${cfg.pepper}' - PI_ENCFILE = '${cfg.encFile}' - PI_AUDIT_KEY_PRIVATE = '${cfg.auditKeyPrivate}' - PI_AUDIT_KEY_PUBLIC = '${cfg.auditKeyPublic}' - PI_LOGCONFIG = '${logCfg}' - ${cfg.extraConfig} - ''; - - renderValue = x: - if isList x then concatMapStringsSep "," (x: ''"${x}"'') x - else if isString x && hasInfix "," x then ''"${x}"'' - else x; - - ldapProxyConfig = pkgs.writeText "ldap-proxy.ini" - (generators.toINI {} - (flip mapAttrs cfg.ldap-proxy.settings - (const (mapAttrs (const renderValue))))); - - privacyidea-token-janitor = pkgs.writeShellScriptBin "privacyidea-token-janitor" '' - exec -a privacyidea-token-janitor \ - /run/wrappers/bin/sudo -u ${cfg.user} \ - env PRIVACYIDEA_CONFIGFILE=${cfg.stateDir}/privacyidea.cfg \ - ${penv}/bin/privacyidea-token-janitor $@ - ''; -in - -{ - options = { - services.privacyidea = { - enable = mkEnableOption (lib.mdDoc "PrivacyIDEA"); - - environmentFile = mkOption { - type = types.nullOr types.path; - default = null; - example = "/root/privacyidea.env"; - description = lib.mdDoc '' - File to load as environment file. Environment variables - from this file will be interpolated into the config file - using `envsubst` which is helpful for specifying - secrets: - ``` - { services.privacyidea.secretKey = "$SECRET"; } - ``` - - The environment-file can now specify the actual secret key: - ``` - SECRET=veryverytopsecret - ``` - ''; - }; - - stateDir = mkOption { - type = types.str; - default = "/var/lib/privacyidea"; - description = lib.mdDoc '' - Directory where all PrivacyIDEA files will be placed by default. - ''; - }; - - superuserRealm = mkOption { - type = types.listOf types.str; - default = [ "super" "administrators" ]; - description = lib.mdDoc '' - The realm where users are allowed to login as administrators. - ''; - }; - - secretKey = mkOption { - type = types.str; - example = "t0p s3cr3t"; - description = lib.mdDoc '' - This is used to encrypt the auth_token. - ''; - }; - - pepper = mkOption { - type = types.str; - example = "Never know..."; - description = lib.mdDoc '' - This is used to encrypt the admin passwords. - ''; - }; - - encFile = mkOption { - type = types.str; - default = "${cfg.stateDir}/enckey"; - defaultText = literalExpression ''"''${config.${opt.stateDir}}/enckey"''; - description = lib.mdDoc '' - This is used to encrypt the token data and token passwords - ''; - }; - - auditKeyPrivate = mkOption { - type = types.str; - default = "${cfg.stateDir}/private.pem"; - defaultText = literalExpression ''"''${config.${opt.stateDir}}/private.pem"''; - description = lib.mdDoc '' - Private Key for signing the audit log. - ''; - }; - - auditKeyPublic = mkOption { - type = types.str; - default = "${cfg.stateDir}/public.pem"; - defaultText = literalExpression ''"''${config.${opt.stateDir}}/public.pem"''; - description = lib.mdDoc '' - Public key for checking signatures of the audit log. - ''; - }; - - adminPasswordFile = mkOption { - type = types.path; - description = lib.mdDoc "File containing password for the admin user"; - }; - - adminEmail = mkOption { - type = types.str; - example = "admin@example.com"; - description = lib.mdDoc "Mail address for the admin user"; - }; - - extraConfig = mkOption { - type = types.lines; - default = ""; - description = lib.mdDoc '' - Extra configuration options for pi.cfg. - ''; - }; - - user = mkOption { - type = types.str; - default = "privacyidea"; - description = lib.mdDoc "User account under which PrivacyIDEA runs."; - }; - - group = mkOption { - type = types.str; - default = "privacyidea"; - description = lib.mdDoc "Group account under which PrivacyIDEA runs."; - }; - - tokenjanitor = { - enable = mkEnableOption (lib.mdDoc "automatic runs of the token janitor"); - interval = mkOption { - default = "quarterly"; - type = types.str; - description = lib.mdDoc '' - Interval in which the cleanup program is supposed to run. - See {manpage}`systemd.time(7)` for further information. - ''; - }; - action = mkOption { - type = types.enum [ "delete" "mark" "disable" "unassign" ]; - description = lib.mdDoc '' - Which action to take for matching tokens. - ''; - }; - unassigned = mkOption { - default = false; - type = types.bool; - description = lib.mdDoc '' - Whether to search for **unassigned** tokens - and apply [](#opt-services.privacyidea.tokenjanitor.action) - onto them. - ''; - }; - orphaned = mkOption { - default = true; - type = types.bool; - description = lib.mdDoc '' - Whether to search for **orphaned** tokens - and apply [](#opt-services.privacyidea.tokenjanitor.action) - onto them. - ''; - }; - }; - - ldap-proxy = { - enable = mkEnableOption (lib.mdDoc "PrivacyIDEA LDAP Proxy"); - - configFile = mkOption { - type = types.nullOr types.path; - default = null; - description = lib.mdDoc '' - Path to PrivacyIDEA LDAP Proxy configuration (proxy.ini). - ''; - }; - - user = mkOption { - type = types.str; - default = "pi-ldap-proxy"; - description = lib.mdDoc "User account under which PrivacyIDEA LDAP proxy runs."; - }; - - group = mkOption { - type = types.str; - default = "pi-ldap-proxy"; - description = lib.mdDoc "Group account under which PrivacyIDEA LDAP proxy runs."; - }; - - settings = mkOption { - type = with types; attrsOf (attrsOf (oneOf [ str bool int (listOf str) ])); - default = {}; - description = lib.mdDoc '' - Attribute-set containing the settings for `privacyidea-ldap-proxy`. - It's possible to pass secrets using env-vars as substitutes and - use the option [](#opt-services.privacyidea.ldap-proxy.environmentFile) - to inject them via `envsubst`. - ''; - }; - - environmentFile = mkOption { - default = null; - type = types.nullOr types.str; - description = lib.mdDoc '' - Environment file containing secrets to be substituted into - [](#opt-services.privacyidea.ldap-proxy.settings). - ''; - }; - }; - }; - }; - - config = mkMerge [ - - (mkIf cfg.enable { - - assertions = [ - { - assertion = cfg.tokenjanitor.enable -> (cfg.tokenjanitor.orphaned || cfg.tokenjanitor.unassigned); - message = '' - privacyidea-token-janitor has no effect if neither orphaned nor unassigned tokens - are to be searched. - ''; - } - ]; - - environment.systemPackages = [ pkgs.privacyidea (hiPrio privacyidea-token-janitor) ]; - - services.postgresql.enable = mkDefault true; - - systemd.services.privacyidea-tokenjanitor = mkIf cfg.tokenjanitor.enable { - environment.PRIVACYIDEA_CONFIGFILE = "${cfg.stateDir}/privacyidea.cfg"; - path = [ penv ]; - serviceConfig = { - CapabilityBoundingSet = [ "" ]; - ExecStart = "${pkgs.writeShellScript "pi-token-janitor" '' - ${optionalString cfg.tokenjanitor.orphaned '' - echo >&2 "Removing orphaned tokens..." - privacyidea-token-janitor find \ - --orphaned true \ - --action ${cfg.tokenjanitor.action} - ''} - ${optionalString cfg.tokenjanitor.unassigned '' - echo >&2 "Removing unassigned tokens..." - privacyidea-token-janitor find \ - --assigned false \ - --action ${cfg.tokenjanitor.action} - ''} - ''}"; - Group = cfg.group; - LockPersonality = true; - MemoryDenyWriteExecute = true; - ProtectHome = true; - ProtectHostname = true; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectSystem = "strict"; - ReadWritePaths = cfg.stateDir; - Type = "oneshot"; - User = cfg.user; - WorkingDirectory = cfg.stateDir; - }; - }; - systemd.timers.privacyidea-tokenjanitor = mkIf cfg.tokenjanitor.enable { - wantedBy = [ "timers.target" ]; - timerConfig.OnCalendar = cfg.tokenjanitor.interval; - timerConfig.Persistent = true; - }; - - systemd.services.privacyidea = let - piuwsgi = pkgs.writeText "uwsgi.json" (builtins.toJSON { - uwsgi = { - buffer-size = 8192; - plugins = [ "python3" ]; - pythonpath = "${penv}/${uwsgi.python3.sitePackages}"; - socket = "/run/privacyidea/socket"; - uid = cfg.user; - gid = cfg.group; - chmod-socket = 770; - chown-socket = "${cfg.user}:nginx"; - chdir = cfg.stateDir; - wsgi-file = "${penv}/etc/privacyidea/privacyideaapp.wsgi"; - processes = 4; - harakiri = 60; - reload-mercy = 8; - stats = "/run/privacyidea/stats.socket"; - max-requests = 2000; - limit-as = 1024; - reload-on-as = 512; - reload-on-rss = 256; - no-orphans = true; - vacuum = true; - }; - }); - in { - wantedBy = [ "multi-user.target" ]; - after = [ "postgresql.service" ]; - path = with pkgs; [ openssl ]; - environment.PRIVACYIDEA_CONFIGFILE = "${cfg.stateDir}/privacyidea.cfg"; - preStart = let - pi-manage = "${config.security.sudo.package}/bin/sudo -u privacyidea -HE ${penv}/bin/pi-manage"; - pgsu = config.services.postgresql.superUser; - psql = config.services.postgresql.package; - in '' - mkdir -p ${cfg.stateDir} /run/privacyidea - chown ${cfg.user}:${cfg.group} -R ${cfg.stateDir} /run/privacyidea - umask 077 - ${lib.getBin pkgs.envsubst}/bin/envsubst -o ${cfg.stateDir}/privacyidea.cfg \ - -i "${piCfgFile}" - chown ${cfg.user}:${cfg.group} ${cfg.stateDir}/privacyidea.cfg - if ! test -e "${cfg.stateDir}/db-created"; then - ${config.security.sudo.package}/bin/sudo -u ${pgsu} ${psql}/bin/createuser --no-superuser --no-createdb --no-createrole ${cfg.user} - ${config.security.sudo.package}/bin/sudo -u ${pgsu} ${psql}/bin/createdb --owner ${cfg.user} privacyidea - ${pi-manage} create_enckey - ${pi-manage} create_audit_keys - ${pi-manage} createdb - ${pi-manage} admin add admin -e ${cfg.adminEmail} -p "$(cat ${cfg.adminPasswordFile})" - ${pi-manage} db stamp head -d ${penv}/lib/privacyidea/migrations - touch "${cfg.stateDir}/db-created" - chmod g+r "${cfg.stateDir}/enckey" "${cfg.stateDir}/private.pem" - fi - ${pi-manage} db upgrade -d ${penv}/lib/privacyidea/migrations - ''; - serviceConfig = { - Type = "notify"; - ExecStart = "${uwsgi}/bin/uwsgi --json ${piuwsgi}"; - ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; - EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; - ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID"; - NotifyAccess = "main"; - KillSignal = "SIGQUIT"; - }; - }; - - users.users.privacyidea = mkIf (cfg.user == "privacyidea") { - group = cfg.group; - isSystemUser = true; - }; - - users.groups.privacyidea = mkIf (cfg.group == "privacyidea") {}; - }) - - (mkIf cfg.ldap-proxy.enable { - - assertions = [ - { assertion = let - xor = a: b: a && !b || !a && b; - in xor (cfg.ldap-proxy.settings == {}) (cfg.ldap-proxy.configFile == null); - message = "configFile & settings are mutually exclusive for services.privacyidea.ldap-proxy!"; - } - ]; - - warnings = mkIf (cfg.ldap-proxy.configFile != null) [ - "Using services.privacyidea.ldap-proxy.configFile is deprecated! Use the RFC42-style settings option instead!" - ]; - - systemd.services.privacyidea-ldap-proxy = let - ldap-proxy-env = pkgs.python3.withPackages (ps: [ ps.privacyidea-ldap-proxy ]); - in { - description = "privacyIDEA LDAP proxy"; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - User = cfg.ldap-proxy.user; - Group = cfg.ldap-proxy.group; - StateDirectory = "privacyidea-ldap-proxy"; - EnvironmentFile = mkIf (cfg.ldap-proxy.environmentFile != null) - [ cfg.ldap-proxy.environmentFile ]; - ExecStartPre = - "${pkgs.writeShellScript "substitute-secrets-ldap-proxy" '' - umask 0077 - ${pkgs.envsubst}/bin/envsubst \ - -i ${ldapProxyConfig} \ - -o $STATE_DIRECTORY/ldap-proxy.ini - ''}"; - ExecStart = let - configPath = if cfg.ldap-proxy.settings != {} - then "%S/privacyidea-ldap-proxy/ldap-proxy.ini" - else cfg.ldap-proxy.configFile; - in '' - ${ldap-proxy-env}/bin/twistd \ - --nodaemon \ - --pidfile= \ - -u ${cfg.ldap-proxy.user} \ - -g ${cfg.ldap-proxy.group} \ - ldap-proxy \ - -c ${configPath} - ''; - Restart = "always"; - }; - }; - - users.users.pi-ldap-proxy = mkIf (cfg.ldap-proxy.user == "pi-ldap-proxy") { - group = cfg.ldap-proxy.group; - isSystemUser = true; - }; - - users.groups.pi-ldap-proxy = mkIf (cfg.ldap-proxy.group == "pi-ldap-proxy") {}; - }) - ]; - -} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 456efe14464..0a3bbf37c6b 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -685,7 +685,6 @@ in { predictable-interface-names = handleTest ./predictable-interface-names.nix {}; printing-socket = handleTest ./printing.nix { socket = true; }; printing-service = handleTest ./printing.nix { socket = false; }; - privacyidea = handleTest ./privacyidea.nix {}; privoxy = handleTest ./privoxy.nix {}; prometheus = handleTest ./prometheus.nix {}; prometheus-exporters = handleTest ./prometheus-exporters.nix {}; diff --git a/nixos/tests/privacyidea.nix b/nixos/tests/privacyidea.nix deleted file mode 100644 index 401ad72c37b..00000000000 --- a/nixos/tests/privacyidea.nix +++ /dev/null @@ -1,43 +0,0 @@ -# Miscellaneous small tests that don't warrant their own VM run. - -import ./make-test-python.nix ({ pkgs, ...} : rec { - name = "privacyidea"; - meta = with pkgs.lib.maintainers; { - maintainers = [ ]; - }; - - nodes.machine = { ... }: { - virtualisation.cores = 2; - - services.privacyidea = { - enable = true; - secretKey = "$SECRET_KEY"; - pepper = "$PEPPER"; - adminPasswordFile = pkgs.writeText "admin-password" "testing"; - adminEmail = "root@localhost"; - - # Don't try this at home! - environmentFile = pkgs.writeText "pi-secrets.env" '' - SECRET_KEY=testing - PEPPER=testing - ''; - }; - services.nginx = { - enable = true; - virtualHosts."_".locations."/".extraConfig = '' - uwsgi_pass unix:/run/privacyidea/socket; - ''; - }; - }; - - testScript = '' - machine.start() - machine.wait_for_unit("multi-user.target") - machine.succeed("curl --fail http://localhost | grep privacyIDEA") - machine.succeed("grep \"SECRET_KEY = 'testing'\" /var/lib/privacyidea/privacyidea.cfg") - machine.succeed("grep \"PI_PEPPER = 'testing'\" /var/lib/privacyidea/privacyidea.cfg") - machine.succeed( - "curl --fail http://localhost/auth -F username=admin -F password=testing | grep token" - ) - ''; -}) diff --git a/pkgs/applications/misc/privacyidea/default.nix b/pkgs/applications/misc/privacyidea/default.nix deleted file mode 100644 index 9cdf4da33ef..00000000000 --- a/pkgs/applications/misc/privacyidea/default.nix +++ /dev/null @@ -1,263 +0,0 @@ -{ lib, fetchFromGitHub, cacert, openssl, nixosTests -, python310, fetchPypi, fetchpatch -}: - -let - dropDocOutput = { outputs, ... }: { - outputs = lib.filter (x: x != "doc") outputs; - }; - - # Follow issue below for Python 3.11 support - # https://github.com/privacyidea/privacyidea/issues/3593 - python3' = python310.override { - packageOverrides = self: super: { - django = super.django_3; - - sqlalchemy = super.sqlalchemy.overridePythonAttrs (oldAttrs: rec { - version = "1.3.24"; - src = fetchPypi { - inherit (oldAttrs) pname; - inherit version; - hash = "sha256-67t3fL+TEjWbiXv4G6ANrg9ctp+6KhgmXcwYpvXvdRk="; - }; - doCheck = false; - }); - # version 3.3.0+ does not support SQLAlchemy 1.3 - factory-boy = super.factory-boy.overridePythonAttrs (oldAttrs: rec { - version = "3.2.1"; - src = oldAttrs.src.override { - inherit version; - hash = "sha256-qY0newwEfHXrbkq4UIp/gfsD0sshmG9ieRNUbveipV4="; - }; - postPatch = ""; - }); - # fails with `no tests ran in 1.75s` - alembic = super.alembic.overridePythonAttrs (lib.const { - doCheck = false; - }); - flask-migrate = super.flask-migrate.overridePythonAttrs (oldAttrs: rec { - version = "2.7.0"; - src = fetchPypi { - pname = "Flask-Migrate"; - inherit version; - hash = "sha256-ri8FZxWIdi3YOiHYsYxR/jVehng+JFlJlf+Nc4Df/jg="; - }; - }); - flask-sqlalchemy = super.flask-sqlalchemy.overridePythonAttrs (old: rec { - version = "2.5.1"; - format = "setuptools"; - src = fetchPypi { - pname = "Flask-SQLAlchemy"; - inherit version; - hash = "sha256:2bda44b43e7cacb15d4e05ff3cc1f8bc97936cc464623424102bfc2c35e95912"; - }; - }); - # Taken from by https://github.com/NixOS/nixpkgs/pull/173090/commits/d2c0c7eb4cc91beb0a1adbaf13abc0a526a21708 - werkzeug = super.werkzeug.overridePythonAttrs (old: rec { - version = "1.0.1"; - src = old.src.override { - inherit version; - hash = "sha256-bICx5a02ZSkOo5MguR4b4eDV9gZSuWSjBwIW3oPS5Hw="; - }; - nativeCheckInputs = old.nativeCheckInputs ++ (with self; [ - requests - ]); - doCheck = false; - }); - # Required by flask-1.1 - jinja2 = super.jinja2.overridePythonAttrs (old: rec { - version = "2.11.3"; - src = old.src.override { - inherit version; - hash = "sha256-ptWEM94K6AA0fKsfowQ867q+i6qdKeZo8cdoy4ejM8Y="; - }; - patches = [ - # python 3.10 compat fixes. In later upstream releases, but these - # are not compatible with flask 1 which we need here :( - (fetchpatch { - url = "https://github.com/thmo/jinja/commit/1efb4cc918b4f3d097c376596da101de9f76585a.patch"; - hash = "sha256-GFaSvYxgzOEFmnnDIfcf0ImScNTh1lR4lxt2Uz1DYdU="; - }) - (fetchpatch { - url = "https://github.com/mkrizek/jinja/commit/bd8bad37d1c0e2d8995a44fd88e234f5340afec5.patch"; - hash = "sha256-Uow+gaO+/dH6zavC0X/SsuMAfhTLRWpamVlL87DXDRA="; - excludes = [ "CHANGES.rst" ]; - }) - ]; - }); - # Required by jinja2-2.11.3 - markupsafe = super.markupsafe.overridePythonAttrs (old: rec { - version = "2.0.1"; - src = old.src.override { - inherit version; - hash = "sha256-WUxngH+xYjizDES99082wCzfItHIzake+KDtjav1Ygo="; - }; - }); - itsdangerous = super.itsdangerous.overridePythonAttrs (old: rec { - version = "1.1.0"; - src = old.src.override { - inherit version; - hash = "sha256-MhsDPQfypBNtPsdi6snxahDM1g9TwMka+QIXrOe6Hxk="; - }; - }); - flask = super.flask.overridePythonAttrs (old: rec { - version = "1.1.4"; - src = old.src.override { - inherit version; - hash = "sha256-D762GA04OpGG0NbtlU4AQq2fGODo3giLK0GdUmkn0ZY="; - }; - }); - sqlsoup = super.sqlsoup.overrideAttrs ({ meta ? {}, ... }: { - meta = meta // { broken = false; }; - }); - click = super.click.overridePythonAttrs (old: rec { - version = "7.1.2"; - src = old.src.override { - inherit version; - hash = "sha256-0rUlXHxjSbwb0eWeCM0SrLvWPOZJ8liHVXg6qU37axo="; - }; - disabledTests = [ "test_bytes_args" ]; # https://github.com/pallets/click/commit/6e05e1fa1c2804 - }); - # Now requires `lingua` as check input that requires a newer `click`, - # however `click-7` is needed by the older flask we need here. Since it's just - # for the test-suite apparently, let's skip it for now. - mako = super.mako.overridePythonAttrs (lib.const { - nativeCheckInputs = []; - doCheck = false; - }); - # Requires pytest-httpserver as checkInput now which requires Werkzeug>=2 which is not - # supported by current privacyIDEA. - responses = super.responses.overridePythonAttrs (lib.const { - doCheck = false; - }); - flask-babel = (super.flask-babel.override { - sphinxHook = null; - furo = null; - }).overridePythonAttrs (old: (dropDocOutput old) // rec { - pname = "Flask-Babel"; - version = "2.0.0"; - format = "setuptools"; - src = fetchPypi { - inherit pname; - inherit version; - hash = "sha256:f9faf45cdb2e1a32ea2ec14403587d4295108f35017a7821a2b1acb8cfd9257d"; - }; - disabledTests = [ - # AssertionError: assert 'Apr 12, 2010...46:00\u202fPM' == 'Apr 12, 2010, 1:46:00 PM' - # Note the `\u202f` (narrow, no-break space) vs space. - "test_basics" - "test_init_app" - "test_custom_locale_selector" - "test_refreshing" - ]; - }); - psycopg2 = (super.psycopg2.override { - sphinxHook = null; - sphinx-better-theme = null; - }).overridePythonAttrs dropDocOutput; - pyjwt = (super.pyjwt.override { - sphinxHook = null; - sphinx-rtd-theme = null; - }).overridePythonAttrs (old: (dropDocOutput old) // { format = "setuptools"; }); - beautifulsoup4 = (super.beautifulsoup4.override { - sphinxHook = null; - }).overridePythonAttrs dropDocOutput; - pydash = (super.pydash.override { - sphinx-rtd-theme = null; - }).overridePythonAttrs (old: rec { - version = "5.1.0"; - src = fetchPypi { - inherit (old) pname; - inherit version; - hash = "sha256-GysFCsG64EnNB/WSCxT6u+UmOPSF2a2h6xFanuv/aDU="; - }; - format = "setuptools"; - doCheck = false; - }); - pyopenssl = (super.pyopenssl.override { - sphinxHook = null; - sphinx-rtd-theme = null; - }).overridePythonAttrs dropDocOutput; - deprecated = (super.deprecated.override { - sphinxHook = null; - }).overridePythonAttrs dropDocOutput; - wrapt = (super.wrapt.override { - sphinxHook = null; - sphinx-rtd-theme = null; - }).overridePythonAttrs dropDocOutput; - }; - }; -in -python3'.pkgs.buildPythonPackage rec { - pname = "privacyIDEA"; - version = "3.8.1"; - format = "setuptools"; - - src = fetchFromGitHub { - owner = pname; - repo = pname; - rev = "v${version}"; - hash = "sha256-SYXw8PBCb514v3rcy15W/vZS5JyMsu81D2sJmviLRtw="; - fetchSubmodules = true; - }; - - patches = [ - # https://github.com/privacyidea/privacyidea/pull/3611 - (fetchpatch { - url = "https://github.com/privacyidea/privacyidea/commit/7db6509721726a34e8528437ddbd4210019b11ef.patch"; - sha256 = "sha256-ZvtauCs1vWyxzGbA0B2+gG8q5JyUO8DF8nm/3/vcYmE="; - }) - ]; - - propagatedBuildInputs = with python3'.pkgs; [ - cryptography pyrad pymysql python-dateutil flask-versioned flask-script - defusedxml croniter flask-migrate pyjwt configobj sqlsoup pillow - python-gnupg passlib pyopenssl beautifulsoup4 smpplib flask-babel - ldap3 huey pyyaml qrcode oauth2client requests lxml cbor2 psycopg2 - pydash ecdsa google-auth importlib-metadata argon2-cffi bcrypt segno - ]; - - passthru.tests = { inherit (nixosTests) privacyidea; }; - - nativeCheckInputs = with python3'.pkgs; [ openssl mock pytestCheckHook responses testfixtures ]; - preCheck = "export HOME=$(mktemp -d)"; - postCheck = "unset HOME"; - disabledTests = [ - # expects `/home/` to exist, fails with `FileNotFoundError: [Errno 2] No such file or directory: '/home/'`. - "test_01_loading_scripts" - - # Tries to connect to `fcm.googleapis.com`. - "test_02_api_push_poll" - "test_04_decline_auth_request" - - # Timezone info not available in build sandbox - "test_14_convert_timestamp_to_utc" - - # Fails because of different logger configurations - "test_01_create_default_app" - "test_03_logging_config_file" - "test_04_logging_config_yaml" - "test_05_logging_config_broken_yaml" - ]; - - pythonImportsCheck = [ "privacyidea" ]; - - postPatch = '' - patchShebangs tests/testdata/scripts - substituteInPlace privacyidea/lib/resolvers/LDAPIdResolver.py --replace \ - "/etc/privacyidea/ldap-ca.crt" \ - "${cacert}/etc/ssl/certs/ca-bundle.crt" - ''; - - postInstall = '' - rm -r $out/${python3'.sitePackages}/tests - ''; - - meta = with lib; { - description = "Multi factor authentication system (2FA, MFA, OTP Server)"; - license = licenses.agpl3Plus; - homepage = "http://www.privacyidea.org"; - maintainers = with maintainers; [ ma27 ]; - platforms = platforms.linux; - }; -} diff --git a/pkgs/development/python-modules/privacyidea-ldap-proxy/default.nix b/pkgs/development/python-modules/privacyidea-ldap-proxy/default.nix deleted file mode 100644 index 0b2a85f7e94..00000000000 --- a/pkgs/development/python-modules/privacyidea-ldap-proxy/default.nix +++ /dev/null @@ -1,32 +0,0 @@ -{ lib, buildPythonPackage, fetchFromGitHub, twisted, ldaptor, configobj, fetchpatch }: - -buildPythonPackage rec { - pname = "privacyidea-ldap-proxy"; - version = "0.7"; - - src = fetchFromGitHub { - owner = "privacyidea"; - repo = pname; - rev = "v${version}"; - sha256 = "1i2kgxqd38xvb42qj0a4a35w4vk0fyp3n7w48kqmvrxc77p6r6i8"; - }; - - patches = [ - # support for LDAPCompareRequest. - (fetchpatch { - url = "https://github.com/mayflower/privacyidea-ldap-proxy/commit/a13356717379b174f1a6abf767faa0dbd459f5dd.patch"; - hash = "sha256-SBTj9ayQ8JFD8BoYIl77nxWVV3PXnHZ8JMlJnxd/nEk="; - }) - ]; - - propagatedBuildInputs = [ twisted ldaptor configobj ]; - - pythonImportsCheck = [ "pi_ldapproxy" ]; - - meta = with lib; { - description = "LDAP Proxy to intercept LDAP binds and authenticate against privacyIDEA"; - homepage = "https://github.com/privacyidea/privacyidea-ldap-proxy"; - license = licenses.agpl3Only; - maintainers = [ ]; - }; -} diff --git a/pkgs/top-level/aliases.nix b/pkgs/top-level/aliases.nix index 85ac74fae7d..38c4126a20f 100644 --- a/pkgs/top-level/aliases.nix +++ b/pkgs/top-level/aliases.nix @@ -710,6 +710,7 @@ mapAliases ({ pinentry_qt = throw "'pinentry_qt' has been renamed to/replaced by 'pinentry-qt'"; # Converted to throw 2023-09-10 pinentry_qt5 = pinentry-qt; # Added 2020-02-11 poetry2nix = throw "poetry2nix is now maintained out-of-tree. Please use https://github.com/nix-community/poetry2nix/"; # Added 2023-10-26 + privacyidea = throw "privacyidea has been removed from nixpkgs"; # Added 2023-10-31 probe-rs-cli = throw "probe-rs-cli is now part of the probe-rs package"; # Added 2023-07-03 processing3 = throw "'processing3' has been renamed to/replaced by 'processing'"; # Converted to throw 2023-09-10 prometheus-dmarc-exporter = dmarc-metrics-exporter; # added 2022-05-31 diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 77aaee8aa69..9d9a3a7a404 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -19871,8 +19871,6 @@ with pkgs; premake = premake4; - privacyidea = callPackage ../applications/misc/privacyidea { }; - process-compose = callPackage ../applications/misc/process-compose { }; process-viewer = callPackage ../applications/misc/process-viewer { }; diff --git a/pkgs/top-level/python-aliases.nix b/pkgs/top-level/python-aliases.nix index f11511b81f9..5e273836060 100644 --- a/pkgs/top-level/python-aliases.nix +++ b/pkgs/top-level/python-aliases.nix @@ -262,7 +262,7 @@ mapAliases ({ poster3 = throw "poster3 is unmaintained and source is no longer available"; # added 2023-05-29 postorius = throw "Please use pkgs.mailmanPackages.postorius"; # added 2022-04-29 powerlineMemSegment = powerline-mem-segment; # added 2021-10-08 - privacyidea = throw "privacyidea has been renamed to pkgs.privacyidea"; # added 2021-06-20 + privacyidea-ldap-proxy = throw "privacyidea-ldap-proxy has been removed from nixpkgs"; # added 2023-10-31 prometheus_client = prometheus-client; # added 2021-06-10 prompt_toolkit = prompt-toolkit; # added 2021-07-22 protonup = protonup-ng; # Added 2022-11-06 diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix index a71c3ed14ea..797fb18a26f 100644 --- a/pkgs/top-level/python-packages.nix +++ b/pkgs/top-level/python-packages.nix @@ -9510,8 +9510,6 @@ self: super: with self; { prison = callPackage ../development/python-modules/prison { }; - privacyidea-ldap-proxy = callPackage ../development/python-modules/privacyidea-ldap-proxy { }; - proboscis = callPackage ../development/python-modules/proboscis { }; process-tests = callPackage ../development/python-modules/process-tests { }; -- cgit 1.4.1 From a4b31637b5f340363cad86d95320e614ec1d27b1 Mon Sep 17 00:00:00 2001 From: Tom Barrett Date: Sun, 8 Oct 2023 14:41:42 +0200 Subject: bitcoind: only use enabled services --- nixos/doc/manual/release-notes/rl-2311.section.md | 2 ++ nixos/modules/services/networking/bitcoind.nix | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'nixos/doc') diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md index 9b432329223..94859f30969 100644 --- a/nixos/doc/manual/release-notes/rl-2311.section.md +++ b/nixos/doc/manual/release-notes/rl-2311.section.md @@ -381,6 +381,8 @@ The module update takes care of the new config syntax and the data itself (user - `fusuma` now enables the following plugins: [appmatcher](https://github.com/iberianpig/fusuma-plugin-appmatcher), [keypress](https://github.com/iberianpig/fusuma-plugin-keypress), [sendkey](https://github.com/iberianpig/fusuma-plugin-sendkey), [tap](https://github.com/iberianpig/fusuma-plugin-tap) and [wmctrl](https://github.com/iberianpig/fusuma-plugin-wmctrl). +- `services.bitcoind` now properly respects the `enable` option. + ## Nixpkgs internals {#sec-release-23.11-nixpkgs-internals} - The use of `sourceRoot = "source";`, `sourceRoot = "source/subdir";`, and similar lines in package derivations using the default `unpackPhase` is deprecated as it requires `unpackPhase` to always produce a directory named "source". Use `sourceRoot = src.name`, `sourceRoot = "${src.name}/subdir";`, or `setSourceRoot = "sourceRoot=$(echo */subdir)";` or similar instead. diff --git a/nixos/modules/services/networking/bitcoind.nix b/nixos/modules/services/networking/bitcoind.nix index a86d52b7202..a48066b43b1 100644 --- a/nixos/modules/services/networking/bitcoind.nix +++ b/nixos/modules/services/networking/bitcoind.nix @@ -3,8 +3,7 @@ with lib; let - - eachBitcoind = config.services.bitcoind; + eachBitcoind = filterAttrs (bitcoindName: cfg: cfg.enable) config.services.bitcoind; rpcUserOpts = { name, ... }: { options = { -- cgit 1.4.1