Inventaire

Inventaire is a libre/free webapp to make inventories and lists of books, and facilitates book sharing. By aggregating individuals' and collectives' book inventories from around the world, Inventaire is kind of a huge, distributed community library.

Try the service in a VM

  1. Install Nix
    Arch Linux
    Bash
    $ pacman --sync --refresh --noconfirm curl git jq nix
    Debian
    Bash
    $ apt install --yes curl git jq nix
    Ubuntu
    Bash
    $ apt install --yes curl git jq nix
  2. Download a configuration file
    # default.nix
    {
      ngipkgs ? import (fetchTarball "https://github.com/ngi-nix/ngipkgs/tarball/main") { },
    }:
    ngipkgs.demo-vm (
      {
        config,
        lib,
        pkgs,
        ...
      }:
      let
        # !!! THIS IS INSECURE, DO NOT DO THIS IN PRODUCTION !!!
        # This is only done like this here to allow easy testing & debugging.
        # Use some secrets management mechanism to track values like this outside of Nix, so they can't leak into the store
        couchdbUser = "inventaire";
        couchdbPassword = "ThisIsNotSecurelyManagedAndImFullyAwareOfThis";
        couchdbPort = 5984;
        elasticPort = 9200;
        inventairePort = 3006;
    
        inventaireServiceDeps = [
          "couchdb.service"
          # If using ElasticSearch instead, change this to: "elasticsearch.service"
          "opensearch.service"
        ];
      in
      {
        # !!! THIS IS INSECURE, DO NOT DO THIS IN PRODUCTION !!!
        # This is just done like this here to allow easy testing & debugging.
        # Use some secrets management mechanism to get a string of a path at runtime here.
        environment.etc."inventaire-config-overrides.cjs".text = ''
          // This is not securely managed, and I'm fully aware of this.
    
          module.exports = {
            db: {
              username: "${couchdbUser}",
              password: "${couchdbPassword}",
            },
          }
        '';
    
        services.couchdb = {
          enable = true;
          port = couchdbPort;
    
          extraConfigFiles = [
            # !!! THIS IS INSECURE, DO NOT DO THIS IN PRODUCTION !!!
            # This is only done like this here to allow easy testing & debugging.
            # Point it at a path that secrets management will produce at runtime instead.
            ((pkgs.formats.ini { }).generate "couchdb-admin-setup.ini" {
              admins = {
                "${couchdbUser}" = "${couchdbPassword}";
              };
            })
          ];
        };
    
        # !!! THIS IS INSECURE, DO NOT DO THIS IN PRODUCTION !!!
        # This is only done like this here to allow easy testing & debugging.
        # Manually complete the CouchDB setup instead.
        systemd.services."couchdb-setup" = {
          description = "Setup of CouchDB";
    
          wantedBy = [
            "couchdb.service"
            "inventaire.service"
          ];
          after = [ "couchdb.service" ];
          before = [ "inventaire.service" ];
    
          path = with pkgs; [
            bash
            coreutils
            curl
            netcat
          ];
    
          serviceConfig.Type = "oneshot";
    
          script = ''
            set -e
    
            echo "Waiting for CouchDB to open its port..."
            timeout 30 bash -c 'until nc -z localhost ${toString config.services.couchdb.port}; do sleep 1; done'
            echo "CouchDB port open."
    
            echo "Creating _user table in CouchDB (if necessary)..."
            set +x # in case it already exists
            curl -X PUT \
              'http://${couchdbUser}:${couchdbPassword}@localhost:${toString config.services.couchdb.port}/_users'
            set -e
            echo "_user table created."
    
            echo "CouchDB should now be configured for Inventaire usage."
          '';
        };
    
        # We're using OpenSearch instead of ElasticSearch here because the latter's packaging in Nixpkgs would require us to opt into unfreely-licensed packages.
        # If unfree packages are not an issue to you, or you just want specifically ElasticSearch, then you may use `services.elasticsearch` here instead.
        services.opensearch = {
          enable = true;
          settings."http.port" = elasticPort;
        };
    
        services.inventaire = {
          enable = true;
          inProductionMode = false; # production mode expects to be running behind nginx, breaks some asset serving
          openFirewall = true;
    
          settings = {
            hostname = "0.0.0.0";
            port = inventairePort;
    
            # CouchDB
            db = {
              hostname = "localhost";
              port = couchdbPort;
            };
    
            # LevelDB
            leveldb = {
              directory = "${config.services.inventaire.stateDir}/db/leveldb";
            };
    
            # ElasticSearch / OpenSearch
            elasticsearch = {
              origin = "http://localhost:${toString elasticPort}";
            };
    
            # OpenStreetMap, so no access token is necessary
            mapTiles = {
              provider = "openstreetmap";
            };
    
            # Storage of downloaded files
            mediaStorage = {
              local = {
                directory = "${config.services.inventaire.stateDir}/storage";
              };
            };
          };
    
          # When using proper secrets management, set a path produced at runtime by your secrets management here instead.
          extraDevelopmentSettingsFile = "/etc/inventaire-config-overrides.cjs";
        };
    
        # We connect to local instances of these, so we might as well ensure they get launched first
        systemd.services."inventaire".wants = inventaireServiceDeps;
        systemd.services."inventaire".after = inventaireServiceDeps;
      }
    
    )
    
  3. Enable binary substituters
    Bash
    $ export NIX_CONFIG='substituters = https://cache.nixos.org/ https://ngi.cachix.org/
    trusted-public-keys = cache.nixos.org-1:6nchdd59x431o0gwypbmraurkbj16zpmqfgspcdshjy= ngi.cachix.org-1:n+cal72roc3qqulxihpv+tw5t42whxmmhpragkrsrow='
  4. Build and run a virtual machine
    Arch Linux, Debian Sid and Ubuntu 25.04
    Bash
    $ nix-build ./default.nix && ./result
    Debian 12 and Ubuntu 24.04/24.10
    Bash
    $ rev=$(nix-instantiate --eval --attr sources.nixpkgs.rev https://github.com/ngi-nix/ngipkgs/archive/master.tar.gz | jq --raw-output)
    $ nix-shell -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/$rev.tar.gz --packages nix --run "nix-build ./default.nix && ./result"
  5. Usage Instructions

    A (very insecure!) example of setting up Inventaire and its dependencies on the local machine.

Options

services.inventaire
services.inventaire.enable

Whether to enable Inventaire server.

Type:
boolean
Default:
false
services.inventaire.extraDevelopmentSettingsFile

Path to a .cjs file that exists at runtime with any additional setting.

Notes:

  • The passed path (if any) will be set up as config/local-development.cjs, to override the built-in defaults and values from the main settings attribute
  • These settings are not world-readable (assuming you're passing a path that isn't generated by Nix), and thus suitable for secrets

Links for settings (make sure to look at the version that's actually packaged):

Type:
null or absolute path
Default:
null
services.inventaire.extraProductionSettingsFile

Path to a .cjs file that exists at runtime with any additional setting.

Notes:

  • The passed path (if any) will be set up as config/local-production.cjs, to override the built-in defaults and values from the main settings attribute
  • These settings are not world-readable (assuming you're passing a path that isn't generated by Nix), and thus suitable for secrets

Links for settings (make sure to look at the version that's actually packaged):

Type:
null or absolute path
Default:
null
services.inventaire.inProductionMode

Whether to run in development or production mode.

Will affect which default settings get loaded, and which of the extra settings files options will be relevant.

Type:
boolean
Default:
false
services.inventaire.openFirewall

Whether to open the port specified in settings in the firewall.

Type:
boolean
Default:
false
services.inventaire.package

The inventaire package to use.

Type:
package
Default:
pkgs.inventaire
Notes:
Missing update script An update script is required for automatically tracking the latest release.
services.inventaire.settings

Settings that you wish to set for the service.

Notes:

  • The settings here will be set up as config/local.cjs, to override the built-in defaults
  • These settings are world-readable, and thus not suitable for secrets

Links for settings (make sure to look at the version that's actually packaged):

Type:
JSON value
Default:
{ hostname = "localhost"; leveldb = { directory = "/var/lib/inventaire/db/leveldb"; }; mediaStorage = { local = { directory = "/var/lib/inventaire/storage"; }; }; port = 3006; }
services.inventaire.stateDir

Directory in which Inventaire will run & put its data into.

Type:
absolute path
Default:
"/var/lib/inventaire"

Examples

Enable Inventaire
{
  config,
  lib,
  pkgs,
  ...
}:
let
  # !!! THIS IS INSECURE, DO NOT DO THIS IN PRODUCTION !!!
  # This is only done like this here to allow easy testing & debugging.
  # Use some secrets management mechanism to track values like this outside of Nix, so they can't leak into the store
  couchdbUser = "inventaire";
  couchdbPassword = "ThisIsNotSecurelyManagedAndImFullyAwareOfThis";
  couchdbPort = 5984;
  elasticPort = 9200;
  inventairePort = 3006;

  inventaireServiceDeps = [
    "couchdb.service"
    # If using ElasticSearch instead, change this to: "elasticsearch.service"
    "opensearch.service"
  ];
in
{
  # !!! THIS IS INSECURE, DO NOT DO THIS IN PRODUCTION !!!
  # This is just done like this here to allow easy testing & debugging.
  # Use some secrets management mechanism to get a string of a path at runtime here.
  environment.etc."inventaire-config-overrides.cjs".text = ''
    // This is not securely managed, and I'm fully aware of this.

    module.exports = {
      db: {
        username: "${couchdbUser}",
        password: "${couchdbPassword}",
      },
    }
  '';

  services.couchdb = {
    enable = true;
    port = couchdbPort;

    extraConfigFiles = [
      # !!! THIS IS INSECURE, DO NOT DO THIS IN PRODUCTION !!!
      # This is only done like this here to allow easy testing & debugging.
      # Point it at a path that secrets management will produce at runtime instead.
      ((pkgs.formats.ini { }).generate "couchdb-admin-setup.ini" {
        admins = {
          "${couchdbUser}" = "${couchdbPassword}";
        };
      })
    ];
  };

  # !!! THIS IS INSECURE, DO NOT DO THIS IN PRODUCTION !!!
  # This is only done like this here to allow easy testing & debugging.
  # Manually complete the CouchDB setup instead.
  systemd.services."couchdb-setup" = {
    description = "Setup of CouchDB";

    wantedBy = [
      "couchdb.service"
      "inventaire.service"
    ];
    after = [ "couchdb.service" ];
    before = [ "inventaire.service" ];

    path = with pkgs; [
      bash
      coreutils
      curl
      netcat
    ];

    serviceConfig.Type = "oneshot";

    script = ''
      set -e

      echo "Waiting for CouchDB to open its port..."
      timeout 30 bash -c 'until nc -z localhost ${toString config.services.couchdb.port}; do sleep 1; done'
      echo "CouchDB port open."

      echo "Creating _user table in CouchDB (if necessary)..."
      set +x # in case it already exists
      curl -X PUT \
        'http://${couchdbUser}:${couchdbPassword}@localhost:${toString config.services.couchdb.port}/_users'
      set -e
      echo "_user table created."

      echo "CouchDB should now be configured for Inventaire usage."
    '';
  };

  # We're using OpenSearch instead of ElasticSearch here because the latter's packaging in Nixpkgs would require us to opt into unfreely-licensed packages.
  # If unfree packages are not an issue to you, or you just want specifically ElasticSearch, then you may use `services.elasticsearch` here instead.
  services.opensearch = {
    enable = true;
    settings."http.port" = elasticPort;
  };

  services.inventaire = {
    enable = true;
    inProductionMode = false; # production mode expects to be running behind nginx, breaks some asset serving
    openFirewall = true;

    settings = {
      hostname = "0.0.0.0";
      port = inventairePort;

      # CouchDB
      db = {
        hostname = "localhost";
        port = couchdbPort;
      };

      # LevelDB
      leveldb = {
        directory = "${config.services.inventaire.stateDir}/db/leveldb";
      };

      # ElasticSearch / OpenSearch
      elasticsearch = {
        origin = "http://localhost:${toString elasticPort}";
      };

      # OpenStreetMap, so no access token is necessary
      mapTiles = {
        provider = "openstreetmap";
      };

      # Storage of downloaded files
      mediaStorage = {
        local = {
          directory = "${config.services.inventaire.stateDir}/storage";
        };
      };
    };

    # When using proper secrets management, set a path produced at runtime by your secrets management here instead.
    extraDevelopmentSettingsFile = "/etc/inventaire-config-overrides.cjs";
  };

  # We connect to local instances of these, so we might as well ensure they get launched first
  systemd.services."inventaire".wants = inventaireServiceDeps;
  systemd.services."inventaire".after = inventaireServiceDeps;
}

This project is funded by NLnet through these subgrants: