Calibre-Web With LDAP on Nixos

I have a Nixos home server running Calibre-Web, below is my config:

  # Calibre-Web
  services.calibre-web= {
    enable = true;
    group = "media";
    listen = {
      ip = "127.0.0.1";
      port = 8083;
    };
    options = {
      calibreLibrary = "/tank/media/ebook";
      enableBookUploading = true;
      enableBookConversion = true;
    };
  };

I also have LLDAP running and would like to use it to log into Cablire-Web. This looks possible based on the following:

https://github.com/janeczku/calibre-web/wiki/LDAP-Login
https://github.com/lldap/lldap/blob/3d8aafaa9d3aa62890966af041dc05822be15716/example_configs/calibre_web.md

However, I can’t get it to work. I think the issues I am having is that I need the python-ldap package installed based on Calibre-Web wiki.

I have tried adding the following to my config as it looks to be the right package.

  environment.systemPackages = [
    pkgs.python311Packages.python-ldap
  ];

However, this is not working. I fear that this is some Nixos specific that need to be done so that the Cablire-Web application can see this package.

Any help?
Thank you

Maybe this works?

services.calibre-web.package = pkgs.calibre-web.overrideAttrs ({ propagatedBuildInputs ? [ ], ... }: {
  propagatedBuildInputs = propagatedBuildInputs ++ [ python3.pkgs.python-ldap ];
});

With this calibre-web should be able to find the LDAP package.

1 Like

A more concise possible solution could be to add the package to the systemd unit’s path using systemd.services.calibre-web.path:

systemd.services.calibre-web.path = [pkgs.python311Packages.python-ldap];

I haven’t tested this though, so YMMV.

Thank you, I have tried both options and they don’t seem to help.

I have been reading thought Calibre-Web wiki in more detail, and it seems you also need the package Flask-SimpleLDAP. As far as I can tell, this has not been package in Nixos.

https://search.nixos.org/packages?channel=23.11&from=0&size=50&sort=relevance&type=packages&query=Flask-SimpleLDAP

I am guessing I am out of luck for now.

Thank you for your help

Does this work for you? GitHub - kip93/nixpkgs at chore/add-flask-simpleldap

If so, I can upstream this to nixpkgs.

Thank you @kip93

I hate to admit it, but I am not really sure if I know what to do with this. Do you need me to test it? If yes, how would I do that?

Thank you

Since I don’t have an LDAP system to test this with, I would really appreciate if you could test it. Happy to guide you with the testing.

For using this new package, you need to:

  1. Update nixpkgs to use my fork
  • If you’re using flakes, doing inputs.nixpkgs.url = "github:kip93/nixpkgs/4f3e5501a81b32cb83efc8a62e4743429ead76a3"; should just work.
  • Otherwise, try adding this to your /etc/nixos/configuration.nix:
    nixpkgs.pkgs = import "${builtins.fetchTarball "https://github.com/kip93/nixpkgs/archive/4f3e5501a81b32cb83efc8a62e4743429ead76a3.tar.gz"}" {
      inherit (config.nixpkgs) config overlays localSystem crossSystem;
    };
    
  1. Let calibre-web know where to find this new package (and the old one we talked about above too)
    services.calibre-web.package = pkgs.calibre-web.overrideAttrs ({ propagatedBuildInputs ? [ ], ... }: {
      propagatedBuildInputs = propagatedBuildInputs ++ [ python3.pkgs.python-ldap python3.pkgs.flask-simpleldap ];
    });
    
  2. Do nixos-rebuild switch and these changes should be applied.

Let me know if you have any questions (:

Thank you for your patience.

I am not using flakes, and I am running into issues updating to your fork. I get the following error:

       error: getting status of '/nix/store/a9s4vb4bczms8a6b29rg0h1q8yx12370-nixos/modules/config/krb5/default.nix': No such file or directory
Cacheable portion of option doc build failed.
Usually this means that an option attribute that ends up in documentation (eg `default` or `description`) depends on the restricted module arguments `config` or `pkgs`.

Rebuild your configuration with `--show-trace` to find the offending location. Remove the references to restricted arguments (eg by escaping their antiquotations or adding a `defaultText`) or disable the sandboxed build for the failing module by setting `meta.buildDocsInSandbox = false`.

error: builder for '/nix/store/111845yrb1f3swzqq4ndpbbkdk51k7s8-lazy-options.json.drv' failed with exit code 1
error: 1 dependencies of derivation '/nix/store/1qa58bj33171j0fdp7c1afjffh1dz9s8-options.json.drv' failed to build
building '/nix/store/simcc72708zylxcyxwvs4dkxlvb22k7p-linux-6.6.18-modules.drv'...
error: 1 dependencies of derivation '/nix/store/srgglp14737i9rqnvsy7rphvk8pcj4vf-nixos-configuration-reference-manpage.drv' failed to build
error: 1 dependencies of derivation '/nix/store/9vxz4ikri9crz3pldb4svb1g98q6ig90-nixos-manual-html.drv' failed to build
error: 1 dependencies of derivation '/nix/store/nadq6bd88lblrw0shab80h9d47c350gg-system-path.drv' failed to build
error: 1 dependencies of derivation '/nix/store/p4dbwbgz9gwd1xhw7cj75j02f1lw24b7-nixos-system-nixos-23.11.4976.79baff8812a0.drv' failed to build

I get the same error on my existing system and a test VM I create for this. Below is the more details on the test VM system:

/etc/nixos/configuration.nix

# Edit this configuration file to define what should be installed on
# your system.  Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running ‘nixos-help’).

{ config, pkgs, ... }:

{
  imports =
    [ # Include the results of the hardware scan.
      ./hardware-configuration.nix
    ];

  meta.buildDocsInSandbox = false;
  nixpkgs.pkgs = import "${builtins.fetchTarball "https://github.com/kip93/nixpkgs/archive/4f3e5501a81b32cb83efc8a62e4743429ead76a3.tar.gz"}" {
    inherit (config.nixpkgs) config overlays localSystem crossSystem;
  };

  # Bootloader.
  boot.loader.grub.enable = true;
  boot.loader.grub.device = "/dev/vda";
  boot.loader.grub.useOSProber = true;

  networking.hostName = "nixos"; # Define your hostname.
  # networking.wireless.enable = true;  # Enables wireless support via wpa_supplicant.

  # Configure network proxy if necessary
  # networking.proxy.default = "http://user:password@proxy:port/";
  # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";

  # Enable networking
  networking.networkmanager.enable = true;

  # Set your time zone.
  time.timeZone = "America/Edmonton";

  # Select internationalisation properties.
  i18n.defaultLocale = "en_CA.UTF-8";

  # Configure keymap in X11
  services.xserver = {
    layout = "us";
    xkbVariant = "";
  };

  # Define a user account. Don't forget to set a password with ‘passwd’.
  users.users.nixos = {
    isNormalUser = true;
    description = "Nixos";
    extraGroups = [ "networkmanager" "wheel" ];
    packages = with pkgs; [];
  };

  # List packages installed in system profile. To search, run:
  # $ nix search wget
  environment.systemPackages = with pkgs; [
  #  vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
  #  wget
  ];

  # Some programs need SUID wrappers, can be configured further or are
  # started in user sessions.
  # programs.mtr.enable = true;
  # programs.gnupg.agent = {
  #   enable = true;
  #   enableSSHSupport = true;
  # };

  # List services that you want to enable:

  # Enable the OpenSSH daemon.
  services.openssh.enable = true;

  # Open ports in the firewall.
  # networking.firewall.allowedTCPPorts = [ ... ];
  # networking.firewall.allowedUDPPorts = [ ... ];
  # Or disable the firewall altogether.
  networking.firewall.enable = false;

  # This value determines the NixOS release from which the default
  # settings for stateful data, like file locations and database versions
  # on your system were taken. It‘s perfectly fine and recommended to leave
  # this value at the release version of the first install of this system.
  # Before changing this value read the documentation for this option
  # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
  system.stateVersion = "23.11"; # Did you read the comment?

}

/etc/nixos/hardware-configuration.nix

# Do not modify this file!  It was generated by ‘nixos-generate-config’
# and may be overwritten by future invocations.  Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:

{
  imports =
    [ (modulesPath + "/profiles/qemu-guest.nix")
    ];

  boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "sr_mod" "virtio_blk" ];
  boot.initrd.kernelModules = [ ];
  boot.kernelModules = [ "kvm-intel" ];
  boot.extraModulePackages = [ ];

  fileSystems."/" =
    { device = "/dev/disk/by-uuid/80223521-92d9-4fb0-b4c7-c89eef1fbaa4";
      fsType = "ext4";
    };

  swapDevices = [ ];

  # Enables DHCP on each ethernet and wireless interface. In case of scripted networking
  # (the default) this is the recommended approach. When using systemd-networkd it's
  # still possible to use this option, but it's recommended to use it in conjunction
  # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
  networking.useDHCP = lib.mkDefault true;
  # networking.interfaces.enp1s0.useDHCP = lib.mkDefault true;

  nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}

Output of sudo nixos-rebuild --upgrade switch --show-trace

building '/nix/store/111845yrb1f3swzqq4ndpbbkdk51k7s8-lazy-options.json.drv'...
error:
       … while calling anonymous lambda

         at /nix/store/a9s4vb4bczms8a6b29rg0h1q8yx12370-nixos/lib/eval-cacheable-options.nix:1:1:

            1| { libPath
             | ^
            2| , pkgsLibPath

       … from call site

         at /nix/store/a9s4vb4bczms8a6b29rg0h1q8yx12370-nixos/lib/make-options-doc/default.nix:57:17:

           56|   filteredOpts = lib.filter (opt: opt.visible && !opt.internal) transformedOpts;
           57|   optionsList = lib.flip map filteredOpts
             |                 ^
           58|    (opt: opt

       … while calling 'flip'

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/trivial.nix:133:16:

          132|   */
          133|   flip = f: a: b: f b a;
             |                ^
          134|

       … from call site

         at /nix/store/a9s4vb4bczms8a6b29rg0h1q8yx12370-nixos/lib/make-options-doc/default.nix:54:13:

           53| let
           54|   rawOpts = lib.optionAttrSetToDocList options;
             |             ^
           55|   transformedOpts = map transformOptions rawOpts;

       … while calling 'optionAttrSetToDocList''

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/options.nix:320:32:

          319|
          320|   optionAttrSetToDocList' = _: options:
             |                                ^
          321|     concatMap (opt:

       … from call site

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/options.nix:358:67:

          357|         # builtins.trace opt.loc
          358|         [ docOption ] ++ optionals subOptionsVisible subOptions) (collect isOption options);
             |                                                                   ^
          359|

       … while calling 'collect'

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/attrsets.nix:552:3:

          551|   # The attribute set to recursively collect.
          552|   attrs:
             |   ^
          553|     if pred attrs then

       … from call site

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/attrsets.nix:553:8:

          552|   attrs:
          553|     if pred attrs then
             |        ^
          554|       [ attrs ]

       … while calling 'isType'

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/types.nix:71:18:

           70|   __attrsFailEvaluation = true;
           71|   isType = type: x: (x._type or "") == type;
             |                  ^
           72|

       … from call site

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:242:28:

          241|           # For definitions that have an associated option
          242|           declaredConfig = mapAttrsRecursiveCond (v: ! isOption v) (_: v: v.value) options;
             |                            ^
          243|

       … while calling 'mapAttrsRecursiveCond'

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/attrsets.nix:731:5:

          730|     # Attribute set to recursively map over.
          731|     set:
             |     ^
          732|     let

       … from call site

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:234:33:

          233|           ({ inherit lib options config specialArgs; } // specialArgs);
          234|         in mergeModules prefix (reverseList collected);
             |                                 ^
          235|

       … while calling 'reverseList'

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/lists.nix:504:17:

          503|   */
          504|   reverseList = xs:
             |                 ^
          505|     let l = length xs; in genList (n: elemAt xs (l - n - 1)) l;

       … from call site

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:229:25:

          228|       merged =
          229|         let collected = collectModules
             |                         ^
          230|           class

       … while calling anonymous lambda

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:445:37:

          444|
          445|     in modulesPath: initialModules: args:
             |                                     ^
          446|       filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args);

       … from call site

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:446:7:

          445|     in modulesPath: initialModules: args:
          446|       filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args);
             |       ^
          447|

       … while calling 'filterModules'

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:413:36:

          412|       # modules recursively. It returns the final list of unique-by-key modules
          413|       filterModules = modulesPath: { disabled, modules }:
             |                                    ^
          414|         let

       … while calling anonymous lambda

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:439:31:

          438|           disabledKeys = concatMap ({ file, disabled }: map (moduleKey file) disabled) disabled;
          439|           keyFilter = filter (attrs: ! elem attrs.key disabledKeys);
             |                               ^
          440|         in map (attrs: attrs.module) (builtins.genericClosure {

       … from call site

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:400:22:

          399|           let
          400|             module = checkModule (loadModule args parentFile "${parentKey}:anon-${toString n}" x);
             |                      ^
          401|             collectedImports = collectStructuredModules module._file module.key module.imports args;

       … while calling anonymous lambda

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:365:11:

          364|         else
          365|           m: m;
             |           ^
          366|

       … from call site

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:400:35:

          399|           let
          400|             module = checkModule (loadModule args parentFile "${parentKey}:anon-${toString n}" x);
             |                                   ^
          401|             collectedImports = collectStructuredModules module._file module.key module.imports args;

       … while calling 'loadModule'

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:336:53:

          335|       # Like unifyModuleSyntax, but also imports paths and calls functions if necessary
          336|       loadModule = args: fallbackFile: fallbackKey: m:
             |                                                     ^
          337|         if isFunction m then

       … from call site

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:354:14:

          353|           throw "Module imports can't be nested lists. Perhaps you meant to remove one level of lists? Definitions: ${showDefs defs}"
          354|         else unifyModuleSyntax (toString m) (toString m) (applyModuleArgsIfFunction (toString m) (import m) args);
             |              ^
          355|

       … while calling 'unifyModuleSyntax'

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:454:34:

          453|      of ‘options’, ‘config’ and ‘imports’ attributes. */
          454|   unifyModuleSyntax = file: key: m:
             |                                  ^
          455|     let

       … from call site

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:354:59:

          353|           throw "Module imports can't be nested lists. Perhaps you meant to remove one level of lists? Definitions: ${showDefs defs}"
          354|         else unifyModuleSyntax (toString m) (toString m) (applyModuleArgsIfFunction (toString m) (import m) args);
             |                                                           ^
          355|

       … while calling 'applyModuleArgsIfFunction'

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:488:39:

          487|
          488|   applyModuleArgsIfFunction = key: f: args@{ config, options, lib, ... }:
             |                                       ^
          489|     if isFunction f then applyModuleArgs key f args else f;

       … from call site

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/modules.nix:489:8:

          488|   applyModuleArgsIfFunction = key: f: args@{ config, options, lib, ... }:
          489|     if isFunction f then applyModuleArgs key f args else f;
             |        ^
          490|

       … while calling 'isFunction'

         at /nix/store/dp6p33hmpxd7w9xdddi3izdn276rk9p0-lib/trivial.nix:443:16:

          442|   */
          443|   isFunction = f: builtins.isFunction f ||
             |                ^
          444|     (f ? __functor && isFunction (f.__functor f));

       error: getting status of '/nix/store/a9s4vb4bczms8a6b29rg0h1q8yx12370-nixos/modules/config/krb5/default.nix': No such file or directory
Cacheable portion of option doc build failed.
Usually this means that an option attribute that ends up in documentation (eg `default` or `description`) depends on the restricted module arguments `config` or `pkgs`.

Rebuild your configuration with `--show-trace` to find the offending location. Remove the references to restricted arguments (eg by escaping their antiquotations or adding a `defaultText`) or disable the sandboxed build for the failing module by setting `meta.buildDocsInSandbox = false`.

error: builder for '/nix/store/111845yrb1f3swzqq4ndpbbkdk51k7s8-lazy-options.json.drv' failed with exit code 1
error: 1 dependencies of derivation '/nix/store/1qa58bj33171j0fdp7c1afjffh1dz9s8-options.json.drv' failed to build
building '/nix/store/simcc72708zylxcyxwvs4dkxlvb22k7p-linux-6.6.18-modules.drv'...
error: 1 dependencies of derivation '/nix/store/srgglp14737i9rqnvsy7rphvk8pcj4vf-nixos-configuration-reference-manpage.drv' failed to build
error: 1 dependencies of derivation '/nix/store/9vxz4ikri9crz3pldb4svb1g98q6ig90-nixos-manual-html.drv' failed to build
error: 1 dependencies of derivation '/nix/store/nadq6bd88lblrw0shab80h9d47c350gg-system-path.drv' failed to build
error: 1 dependencies of derivation '/nix/store/p4dbwbgz9gwd1xhw7cj75j02f1lw24b7-nixos-system-nixos-23.11.4976.79baff8812a0.drv' failed to build

Thank you

This looks like something that is not working as expected on master, unrelated to my change.

Try this (getting rid of the stuff from my other comment above):

{ pkgs, ... }: {
  services.calibre-web.package = pkgs.calibre-web.overrideAttrs ({ propagatedBuildInputs ? [ ], ... }: {
    propagatedBuildInputs = propagatedBuildInputs ++ [
      pkgs.python3.pkgs.python-ldap
      (pkgs.python3.pkgs.callPackage "${builtins.fetchTarball "https://github.com/kip93/nixpkgs/archive/4f3e5501a81b32cb83efc8a62e4743429ead76a3.tar.gz"}/pkgs/development/python-modules/flask-simpleldap/default.nix" { })
    ];
  });
}

Here we just extract the one file that matters, so that other unrelated changes don’t break things.

That worked, feel free to upstream it to nixpkgs.

Thank you so much for your time and patience.

1 Like

Great to know! Then tomorrow I’ll upstream both this new package as well as a patch to make sure that calibre-web can easily use these LDAP packages without having to use overrideAttrs.

1 Like