nixos/meilisearch: generic settings; handle secrets better.

This commit is contained in:
sodiboo 2025-07-11 22:57:29 +02:00
parent 75f5596652
commit 5a8660f10f
5 changed files with 174 additions and 106 deletions

View File

@ -882,9 +882,7 @@
"index.html#module-services-meilisearch-quickstart-search"
],
"module-services-meilisearch-defaults": [
"index.html#module-services-meilisearch-defaults"
],
"module-services-meilisearch-missing": [
"index.html#module-services-meilisearch-defaults",
"index.html#module-services-meilisearch-missing"
],
"module-services-networking-yggdrasil": [

View File

@ -32,10 +32,12 @@ you first need to add documents to an index before you can search for documents.
- The default nixos package doesn't come with the [dashboard](https://docs.meilisearch.com/learn/getting_started/quick_start.html#search), since the dashboard features makes some assets downloads at compile time.
- Anonymized Analytics sent to meilisearch are disabled by default.
- `no_analytics` is set to true by default.
- Default deployment is development mode. It doesn't require a secret master key. All routes are not protected and accessible.
- `http_addr` is derived from {option}`services.meilisearch.listenAddress` and {option}`services.meilisearch.listenPort`. The two sub-fields are separate because this makes it easier to consume in certain other modules.
## Missing {#module-services-meilisearch-missing}
- `db_path` is set to `/var/lib/meilisearch` by default. Upstream, the default value is equivalent to `/var/lib/meilisearch/data.ms`.
- the snapshot feature is not yet configurable from the module, it's just a matter of adding the relevant environment variables.
- `dump_dir` and `snapshot_dir` are set to `/var/lib/meilisearch/dumps` and `/var/lib/meilisearch/snapshots`, respectively. This is equivalent to the upstream defaults.
- All other options inherit their upstream defaults. In particular, the default configuration uses `env = "development"`, which doesn't require a master key, in which case all routes are unprotected.

View File

@ -7,19 +7,91 @@
let
cfg = config.services.meilisearch;
settingsFormat = pkgs.formats.toml { };
# These secrets are used in the config file and can be set to paths.
secrets-with-path =
builtins.map
(
{ environment, name }:
{
inherit name environment;
setting = cfg.settings.${name};
}
)
[
{
environment = "MEILI_SSL_CERT_PATH";
name = "ssl_cert_path";
}
{
environment = "MEILI_SSL_KEY_PATH";
name = "ssl_key_path";
}
{
environment = "MEILI_SSL_AUTH_PATH";
name = "ssl_auth_path";
}
{
environment = "MEILI_SSL_OCSP_PATH";
name = "ssl_ocsp_path";
}
];
# We also handle `master_key` separately.
# It cannot be set to a path, so we template it.
master-key-placeholder = "@MASTER_KEY@";
configFile = settingsFormat.generate "config.toml" (
builtins.removeAttrs (
if cfg.masterKeyFile != null then
cfg.settings // { master_key = master-key-placeholder; }
else
builtins.removeAttrs cfg.settings [ "master_key" ]
) (map (secret: secret.name) secrets-with-path)
);
in
{
meta.maintainers = with lib.maintainers; [
Br1ght0ne
happysalada
];
meta.doc = ./meilisearch.md;
###### interface
imports = [
(lib.mkRenamedOptionModule
[ "services" "meilisearch" "environment" ]
[ "services" "meilisearch" "settings" "env" ]
)
(lib.mkRenamedOptionModule
[ "services" "meilisearch" "logLevel" ]
[ "services" "meilisearch" "settings" "log_level" ]
)
(lib.mkRenamedOptionModule
[ "services" "meilisearch" "noAnalytics" ]
[ "services" "meilisearch" "settings" "no_analytics" ]
)
(lib.mkRenamedOptionModule
[ "services" "meilisearch" "maxIndexSize" ]
[ "services" "meilisearch" "settings" "max_index_size" ]
)
(lib.mkRenamedOptionModule
[ "services" "meilisearch" "payloadSizeLimit" ]
[ "services" "meilisearch" "settings" "http_payload_size_limit" ]
)
(lib.mkRenamedOptionModule
[ "services" "meilisearch" "dumplessUpgrade" ]
[ "services" "meilisearch" "settings" "experimental_dumpless_upgrade" ]
)
(lib.mkRemovedOptionModule [ "services" "meilisearch" "masterKeyEnvironmentFile" ] ''
Use `services.meilisearch.masterKeyFile` instead. It does not require you to prefix the file with "MEILI_MASTER_KEY=".
If you were abusing this option to set other options, you can now configure them with `services.meilisearch.settings`.
'')
];
options.services.meilisearch = {
enable = lib.mkEnableOption "MeiliSearch - a RESTful search API";
enable = lib.mkEnableOption "Meilisearch - a RESTful search API";
package = lib.mkPackageOption pkgs "meilisearch" {
extraDescription = ''
@ -28,106 +100,87 @@ in
};
listenAddress = lib.mkOption {
description = "MeiliSearch listen address.";
default = "127.0.0.1";
default = "localhost";
type = lib.types.str;
description = ''
The IP address that Meilisearch will listen on.
It can also be a hostname like "localhost". If it resolves to an IPv4 and IPv6 address, Meilisearch will listen on both.
'';
};
listenPort = lib.mkOption {
description = "MeiliSearch port to listen on.";
default = 7700;
type = lib.types.port;
description = ''
The port that Meilisearch will listen on.
'';
};
environment = lib.mkOption {
description = "Defines the running environment of MeiliSearch.";
default = "development";
type = lib.types.enum [
"development"
"production"
];
};
# TODO change this to LoadCredentials once possible
masterKeyEnvironmentFile = lib.mkOption {
masterKeyFile = lib.mkOption {
description = ''
Path to file which contains the master key.
By doing so, all routes will be protected and will require a key to be accessed.
If no master key is provided, all routes can be accessed without requiring any key.
The format is the following:
MEILI_MASTER_KEY=my_secret_key
'';
default = null;
type = with lib.types; nullOr path;
type = lib.types.nullOr lib.types.path;
};
noAnalytics = lib.mkOption {
settings = lib.mkOption {
description = ''
Deactivates analytics.
Analytics allow MeiliSearch to know how many users are using MeiliSearch,
which versions and which platforms are used.
This process is entirely anonymous.
Configuration settings for Meilisearch.
Look at the documentation for available options:
https://github.com/meilisearch/meilisearch/blob/main/config.toml
https://www.meilisearch.com/docs/learn/self_hosted/configure_meilisearch_at_launch#all-instance-options
'';
default = true;
type = lib.types.bool;
default = { };
type = lib.types.submodule {
freeformType = settingsFormat.type;
imports = builtins.map (secret: {
# give them proper types, just so they're easier to consume from this file
options.${secret.name} = lib.mkOption {
# but they should not show up in documentation as special in any way.
visible = false;
type = lib.types.nullOr lib.types.path;
default = null;
};
}) secrets-with-path;
};
};
logLevel = lib.mkOption {
description = ''
Defines how much detail should be present in MeiliSearch's logs.
MeiliSearch currently supports four log levels, listed in order of increasing verbosity:
- 'ERROR': only log unexpected events indicating MeiliSearch is not functioning as expected
- 'WARN:' log all unexpected events, regardless of their severity
- 'INFO:' log all events. This is the default value
- 'DEBUG': log all events and including detailed information on MeiliSearch's internal processes.
Useful when diagnosing issues and debugging
'';
default = "INFO";
type = lib.types.str;
};
maxIndexSize = lib.mkOption {
description = ''
Sets the maximum size of the index.
Value must be given in bytes or explicitly stating a base unit.
For example, the default value can be written as 107374182400, '107.7Gb', or '107374 Mb'.
Default is 100 GiB
'';
default = "107374182400";
type = lib.types.str;
};
payloadSizeLimit = lib.mkOption {
description = ''
Sets the maximum size of accepted JSON payloads.
Value must be given in bytes or explicitly stating a base unit.
For example, the default value can be written as 107374182400, '107.7Gb', or '107374 Mb'.
Default is ~ 100 MB
'';
default = "104857600";
type = lib.types.str;
};
# TODO: turn on by default when it stops being experimental
dumplessUpgrade = lib.mkOption {
default = false;
example = true;
description = ''
Whether to enable (experimental) dumpless upgrade.
Allows upgrading from Meilisearch >=v1.12 to Meilisearch >=v1.13 without manually
dumping and importing the database.
More information at https://www.meilisearch.com/docs/learn/update_and_migration/updating#dumpless-upgrade
'';
type = lib.types.bool;
};
};
###### implementation
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = !cfg.settings ? master_key;
message = ''
Do not set `services.meilisearch.settings.master_key` in your configuration.
Use `services.meilisearch.masterKeyFile` instead.
'';
}
];
services.meilisearch.settings = {
# we use `listenAddress` and `listenPort` to derive the `http_addr` setting.
# this is the only setting we treat like this.
# we do this because some dependent services like Misskey/Sharkey need separate host,port for no good reason.
http_addr = "${cfg.listenAddress}:${toString cfg.listenPort}";
# upstream's default for `db_path` is `/var/lib/meilisearch/data.ms/`, but ours is different for no reason.
db_path = lib.mkDefault "/var/lib/meilisearch";
# these are equivalent to the upstream defaults, because we set a working directory.
# they are only set here for consistency with `db_path`.
dump_dir = lib.mkDefault "/var/lib/meilisearch/dumps";
snapshot_dir = lib.mkDefault "/var/lib/meilisearch/snapshots";
# this is intentionally different from upstream's default.
no_analytics = lib.mkDefault true;
};
warnings = lib.optional (lib.versionOlder cfg.package.version "1.12") ''
Meilisearch 1.11 will be removed in NixOS 25.11. As it was the last
@ -149,24 +202,41 @@ in
environment.systemPackages = [ cfg.package ];
systemd.services.meilisearch = {
description = "MeiliSearch daemon";
description = "Meilisearch daemon";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
environment = {
MEILI_DB_PATH = "/var/lib/meilisearch";
MEILI_HTTP_ADDR = "${cfg.listenAddress}:${toString cfg.listenPort}";
MEILI_NO_ANALYTICS = lib.boolToString cfg.noAnalytics;
MEILI_ENV = cfg.environment;
MEILI_DUMP_DIR = "/var/lib/meilisearch/dumps";
MEILI_LOG_LEVEL = cfg.logLevel;
MEILI_MAX_INDEX_SIZE = cfg.maxIndexSize;
MEILI_EXPERIMENTAL_DUMPLESS_UPGRADE = lib.boolToString cfg.dumplessUpgrade;
};
preStart = lib.mkMerge [
''
install -m 700 '${configFile}' "$RUNTIME_DIRECTORY/config.toml"
''
(lib.mkIf (cfg.masterKeyFile != null) ''
${lib.getExe pkgs.replace-secret} '${master-key-placeholder}' "$CREDENTIALS_DIRECTORY/master_key" "$RUNTIME_DIRECTORY/config.toml"
'')
];
environment = builtins.listToAttrs (
builtins.map (secret: {
name = secret.environment;
value = lib.mkIf (secret.setting != null) "%d/${secret.name}";
}) secrets-with-path
);
serviceConfig = {
ExecStart = "${cfg.package}/bin/meilisearch";
LoadCredential = lib.mkMerge (
[
(lib.mkIf (cfg.masterKeyFile != null) [ "master_key:${cfg.masterKeyFile}" ])
]
++ builtins.map (
secret: lib.mkIf (secret.setting != null) [ "${secret.name}:${secret.setting}" ]
) secrets-with-path
);
ExecStart = "${lib.getExe cfg.package} --config-file-path \${RUNTIME_DIRECTORY}/config.toml";
DynamicUser = true;
StateDirectory = "meilisearch";
EnvironmentFile = lib.mkIf (cfg.masterKeyEnvironmentFile != null) cfg.masterKeyEnvironmentFile;
WorkingDirectory = "%S/meilisearch";
RuntimeDirectory = "meilisearch";
RuntimeDirectoryMode = "0700";
};
};
};

View File

@ -54,7 +54,7 @@ in
description = ''
Whether to automatically set up a local Meilisearch instance and configure Sharkey to use it.
You need to ensure `services.meilisearch.masterKeyEnvironmentFile` is correctly configured for a working
You need to ensure `services.meilisearch.masterKeyFile` is correctly configured for a working
Meilisearch setup. You also need to configure Sharkey to use an API key obtained from Meilisearch with the
`MK_CONFIG_MEILISEARCH_APIKEY` environment variable, and set `services.sharkey.settings.meilisearch.index` to
the created index. See https://docs.joinsharkey.org/docs/customisation/search/meilisearch/ for how to create
@ -240,7 +240,7 @@ in
(mkIf cfg.setupMeilisearch {
services.meilisearch = {
enable = mkDefault true;
environment = mkDefault "production";
settings.env = mkDefault "production";
};
services.sharkey.settings = {

View File

@ -19,9 +19,7 @@ in
};
};
services.meilisearch.masterKeyEnvironmentFile = pkgs.writeText "meilisearch-key" ''
MEILI_MASTER_KEY=${meilisearchKey}
'';
services.meilisearch.masterKeyFile = pkgs.writeText "meilisearch-key" meilisearchKey;
};
testScript =