push sheeet
Some checks failed
Periodic Merges (6h) / master → staging-nixos (push) Failing after 12m50s
Periodic Merges (6h) / master → staging-next (push) Failing after 12m54s
Periodic Merges (24h) / merge-base(master,staging) → haskell-updates (push) Failing after 11m54s
Periodic Merges (6h) / staging-next → staging (push) Failing after 12m13s
Periodic Merges (24h) / staging-next-25.05 → staging-25.05 (push) Failing after 13m24s
Periodic Merges (24h) / release-25.05 → staging-next-25.05 (push) Failing after 14m28s

This commit is contained in:
Dark Steveneq
2025-10-09 14:15:47 +02:00
commit 646b892680
49168 changed files with 5897842 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.elasticsearch-curator;
curatorConfig = pkgs.writeTextFile {
name = "config.yaml";
text = ''
---
# Remember, leave a key empty if there is no value. None will be a string,
# not a Python "NoneType"
client:
hosts: ${builtins.toJSON cfg.hosts}
port: ${toString cfg.port}
url_prefix:
use_ssl: False
certificate:
client_cert:
client_key:
ssl_no_validate: False
http_auth:
timeout: 30
master_only: False
logging:
loglevel: INFO
logfile:
logformat: default
blacklist: ['elasticsearch', 'urllib3']
'';
};
curatorAction = pkgs.writeTextFile {
name = "action.yaml";
text = cfg.actionYAML;
};
in
{
options.services.elasticsearch-curator = {
enable = mkEnableOption "elasticsearch curator";
interval = mkOption {
description = "The frequency to run curator, a systemd.time such as 'hourly'";
default = "hourly";
type = types.str;
};
hosts = mkOption {
description = "a list of elasticsearch hosts to connect to";
type = types.listOf types.str;
default = [ "localhost" ];
};
port = mkOption {
description = "the port that elasticsearch is listening on";
type = types.port;
default = 9200;
};
actionYAML = mkOption {
description = "curator action.yaml file contents, alternatively use curator-cli which takes a simple action command";
type = types.lines;
example = ''
---
actions:
1:
action: delete_indices
description: >-
Delete indices older than 45 days (based on index name), for logstash-
prefixed indices. Ignore the error if the filter does not result in an
actionable list of indices (ignore_empty_list) and exit cleanly.
options:
ignore_empty_list: True
disable_action: False
filters:
- filtertype: pattern
kind: prefix
value: logstash-
- filtertype: age
source: name
direction: older
timestring: '%Y.%m.%d'
unit: days
unit_count: 45
'';
};
};
config = mkIf cfg.enable {
systemd.services.elasticsearch-curator = {
startAt = cfg.interval;
serviceConfig = {
ExecStart =
"${pkgs.elasticsearch-curator}/bin/curator" + " --config ${curatorConfig} ${curatorAction}";
};
};
};
}

View File

@@ -0,0 +1,239 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.elasticsearch;
es7 = builtins.compareVersions cfg.package.version "7" >= 0;
esConfig = ''
network.host: ${cfg.listenAddress}
cluster.name: ${cfg.cluster_name}
${lib.optionalString cfg.single_node "discovery.type: single-node"}
${lib.optionalString (cfg.single_node && es7) "gateway.auto_import_dangling_indices: true"}
http.port: ${toString cfg.port}
transport.port: ${toString cfg.tcp_port}
${cfg.extraConf}
'';
configDir = cfg.dataDir + "/config";
elasticsearchYml = pkgs.writeTextFile {
name = "elasticsearch.yml";
text = esConfig;
};
loggingConfigFilename = "log4j2.properties";
loggingConfigFile = pkgs.writeTextFile {
name = loggingConfigFilename;
text = cfg.logging;
};
esPlugins = pkgs.buildEnv {
name = "elasticsearch-plugins";
paths = cfg.plugins;
postBuild = "${pkgs.coreutils}/bin/mkdir -p $out/plugins";
};
in
{
###### interface
options.services.elasticsearch = {
enable = mkOption {
description = "Whether to enable elasticsearch.";
default = false;
type = types.bool;
};
package = mkPackageOption pkgs "elasticsearch" { };
listenAddress = mkOption {
description = "Elasticsearch listen address.";
default = "127.0.0.1";
type = types.str;
};
port = mkOption {
description = "Elasticsearch port to listen for HTTP traffic.";
default = 9200;
type = types.port;
};
tcp_port = mkOption {
description = "Elasticsearch port for the node to node communication.";
default = 9300;
type = types.port;
};
cluster_name = mkOption {
description = "Elasticsearch name that identifies your cluster for auto-discovery.";
default = "elasticsearch";
type = types.str;
};
single_node = mkOption {
description = "Start a single-node cluster";
default = true;
type = types.bool;
};
extraConf = mkOption {
description = "Extra configuration for elasticsearch.";
default = "";
type = types.str;
example = ''
node.name: "elasticsearch"
node.master: true
node.data: false
'';
};
logging = mkOption {
description = "Elasticsearch logging configuration.";
default = ''
logger.action.name = org.elasticsearch.action
logger.action.level = info
appender.console.type = Console
appender.console.name = console
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
rootLogger.level = info
rootLogger.appenderRef.console.ref = console
'';
type = types.str;
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/elasticsearch";
description = ''
Data directory for elasticsearch.
'';
};
extraCmdLineOptions = mkOption {
description = "Extra command line options for the elasticsearch launcher.";
default = [ ];
type = types.listOf types.str;
};
extraJavaOptions = mkOption {
description = "Extra command line options for Java.";
default = [ ];
type = types.listOf types.str;
example = [ "-Djava.net.preferIPv4Stack=true" ];
};
plugins = mkOption {
description = "Extra elasticsearch plugins";
default = [ ];
type = types.listOf types.package;
example = lib.literalExpression "[ pkgs.elasticsearchPlugins.discovery-ec2 ]";
};
restartIfChanged = mkOption {
type = types.bool;
description = ''
Automatically restart the service on config change.
This can be set to false to defer restarts on a server or cluster.
Please consider the security implications of inadvertently running an older version,
and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
'';
default = true;
};
};
###### implementation
config = mkIf cfg.enable {
systemd.services.elasticsearch = {
description = "Elasticsearch Daemon";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
path = [ pkgs.inetutils ];
inherit (cfg) restartIfChanged;
environment = {
ES_HOME = cfg.dataDir;
ES_JAVA_OPTS = toString cfg.extraJavaOptions;
ES_PATH_CONF = configDir;
};
serviceConfig = {
ExecStart = "${cfg.package}/bin/elasticsearch ${toString cfg.extraCmdLineOptions}";
User = "elasticsearch";
PermissionsStartOnly = true;
LimitNOFILE = "1024000";
Restart = "always";
TimeoutStartSec = "infinity";
};
preStart = ''
${optionalString (!config.boot.isContainer) ''
# Only set vm.max_map_count if lower than ES required minimum
# This avoids conflict if configured via boot.kernel.sysctl
if [ `${pkgs.procps}/bin/sysctl -n vm.max_map_count` -lt 262144 ]; then
${pkgs.procps}/bin/sysctl -w vm.max_map_count=262144
fi
''}
mkdir -m 0700 -p ${cfg.dataDir}
# Install plugins
ln -sfT ${esPlugins}/plugins ${cfg.dataDir}/plugins
ln -sfT ${cfg.package}/lib ${cfg.dataDir}/lib
ln -sfT ${cfg.package}/modules ${cfg.dataDir}/modules
# elasticsearch needs to create the elasticsearch.keystore in the config directory
# so this directory needs to be writable.
mkdir -m 0700 -p ${configDir}
# Note that we copy config files from the nix store instead of symbolically linking them
# because otherwise X-Pack Security will raise the following exception:
# java.security.AccessControlException:
# access denied ("java.io.FilePermission" "/var/lib/elasticsearch/config/elasticsearch.yml" "read")
cp ${elasticsearchYml} ${configDir}/elasticsearch.yml
# Make sure the logging configuration for old elasticsearch versions is removed:
rm -f "${configDir}/logging.yml"
cp ${loggingConfigFile} ${configDir}/${loggingConfigFilename}
mkdir -p ${configDir}/scripts
cp ${cfg.package}/config/jvm.options ${configDir}/jvm.options
# redirect jvm logs to the data directory
mkdir -m 0700 -p ${cfg.dataDir}/logs
${pkgs.sd}/bin/sd 'logs/gc.log' '${cfg.dataDir}/logs/gc.log' ${configDir}/jvm.options \
if [ "$(id -u)" = 0 ]; then chown -R elasticsearch:elasticsearch ${cfg.dataDir}; fi
'';
postStart = ''
# Make sure elasticsearch is up and running before dependents
# are started
while ! ${pkgs.curl}/bin/curl -sS -f http://${cfg.listenAddress}:${toString cfg.port} 2>/dev/null; do
sleep 1
done
'';
};
environment.systemPackages = [ cfg.package ];
users = {
groups.elasticsearch.gid = config.ids.gids.elasticsearch;
users.elasticsearch = {
uid = config.ids.uids.elasticsearch;
description = "Elasticsearch daemon user";
home = cfg.dataDir;
group = "elasticsearch";
};
};
};
}

View File

@@ -0,0 +1,126 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.hound;
settingsFormat = pkgs.formats.json { };
houndConfigFile = pkgs.writeTextFile {
name = "hound-config.json";
text = builtins.toJSON cfg.settings;
checkPhase = ''
${cfg.package}/bin/houndd -check-conf -conf $out
'';
};
in
{
imports = [
(lib.mkRemovedOptionModule [
"services"
"hound"
"extraGroups"
] "Use users.users.hound.extraGroups instead")
(lib.mkChangedOptionModule [ "services" "hound" "config" ] [ "services" "hound" "settings" ] (
config: builtins.fromJSON config.services.hound.config
))
];
meta.maintainers = with lib.maintainers; [ SuperSandro2000 ];
options = {
services.hound = {
enable = lib.mkEnableOption "hound";
package = lib.mkPackageOption pkgs "hound" { };
user = lib.mkOption {
default = "hound";
type = lib.types.str;
description = ''
User the hound daemon should execute under.
'';
};
group = lib.mkOption {
default = "hound";
type = lib.types.str;
description = ''
Group the hound daemon should execute under.
'';
};
home = lib.mkOption {
default = "/var/lib/hound";
type = lib.types.path;
description = ''
The path to use as hound's $HOME.
If the default user "hound" is configured then this is the home of the "hound" user.
'';
};
settings = lib.mkOption {
type = settingsFormat.type;
example = lib.literalExpression ''
{
max-concurrent-indexers = 2;
repos.nixpkgs.url = "https://www.github.com/NixOS/nixpkgs.git";
}
'';
description = ''
The full configuration of the Hound daemon.
See the upstream documentation <https://github.com/hound-search/hound/blob/main/docs/config-options.md> for details.
:::{.note}
The `dbpath` should be an absolute path to a writable directory.
:::.com/hound-search/hound/blob/main/docs/config-options.md>.
'';
};
listen = lib.mkOption {
type = lib.types.str;
default = "0.0.0.0:6080";
example = ":6080";
description = ''
Listen on this [IP]:port
'';
};
};
};
config = lib.mkIf cfg.enable {
users.groups = lib.mkIf (cfg.group == "hound") {
hound = { };
};
users.users = lib.mkIf (cfg.user == "hound") {
hound = {
description = "Hound code search";
createHome = true;
isSystemUser = true;
inherit (cfg) home group;
};
};
environment.etc."hound/config.json".source = houndConfigFile;
services.hound.settings = {
dbpath = "${config.services.hound.home}/data";
};
systemd.services.hound = {
description = "Hound Code Search";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
restartTriggers = [ houndConfigFile ];
serviceConfig = {
User = cfg.user;
Group = cfg.group;
WorkingDirectory = cfg.home;
ExecStartPre = "${pkgs.git}/bin/git config --global --replace-all http.sslCAinfo ${config.security.pki.caBundle}";
ExecStart = "${cfg.package}/bin/houndd -addr ${cfg.listen} -conf /etc/hound/config.json";
};
};
};
}

View File

@@ -0,0 +1,148 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.manticore;
format = pkgs.formats.json { };
toSphinx =
{
mkKeyValue ? lib.generators.mkKeyValueDefault { } "=",
listsAsDuplicateKeys ? true,
}:
attrsOfAttrs:
let
# map function to string for each key val
mapAttrsToStringsSep =
sep: mapFn: attrs:
lib.concatStringsSep sep (lib.mapAttrsToList mapFn attrs);
mkSection =
sectName: sectValues:
''
${sectName} {
''
+ lib.generators.toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } sectValues
+ ''}'';
in
# map input to ini sections
mapAttrsToStringsSep "\n" mkSection attrsOfAttrs;
configFile = pkgs.writeText "manticore.conf" (
toSphinx {
mkKeyValue = k: v: " ${k} = ${v}";
} cfg.settings
);
in
{
options = {
services.manticore = {
enable = lib.mkEnableOption "Manticoresearch";
settings = lib.mkOption {
default = {
searchd = {
listen = [
"127.0.0.1:9312"
"127.0.0.1:9306:mysql"
"127.0.0.1:9308:http"
];
log = "/var/log/manticore/searchd.log";
query_log = "/var/log/manticore/query.log";
pid_file = "/run/manticore/searchd.pid";
data_dir = "/var/lib/manticore";
};
};
description = ''
Configuration for Manticoresearch. See
<https://manual.manticoresearch.com/Server%20settings>
for more information.
'';
type = lib.types.submodule {
freeformType = format.type;
};
example = lib.literalExpression ''
{
searchd = {
listen = [
"127.0.0.1:9312"
"127.0.0.1:9306:mysql"
"127.0.0.1:9308:http"
];
log = "/var/log/manticore/searchd.log";
query_log = "/var/log/manticore/query.log";
pid_file = "/run/manticore/searchd.pid";
data_dir = "/var/lib/manticore";
};
}
'';
};
};
};
config = lib.mkIf cfg.enable {
systemd = {
packages = [ pkgs.manticoresearch ];
services.manticore = {
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
ExecStart = [
""
"${pkgs.manticoresearch}/bin/searchd --config ${configFile}"
];
ExecStop = [
""
"${pkgs.manticoresearch}/bin/searchd --config ${configFile} --stopwait"
];
ExecStartPre = [ "" ];
DynamicUser = true;
LogsDirectory = "manticore";
RuntimeDirectory = "manticore";
StateDirectory = "manticore";
ReadWritePaths = "";
CapabilityBoundingSet = "";
RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
PrivateDevices = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
];
RestrictRealtime = true;
LockPersonality = true;
MemoryDenyWriteExecute = true;
UMask = "0066";
ProtectHostname = true;
}
// lib.optionalAttrs (cfg.settings.searchd.pid_file != null) {
PIDFile = cfg.settings.searchd.pid_file;
};
};
};
};
meta.maintainers = with lib.maintainers; [ onny ];
}

View File

@@ -0,0 +1,41 @@
# Meilisearch {#module-services-meilisearch}
Meilisearch is a lightweight, fast and powerful search engine. Think elastic search with a much smaller footprint.
## Quickstart {#module-services-meilisearch-quickstart}
the minimum to start meilisearch is
```nix
{ services.meilisearch.enable = true; }
```
this will start the http server included with meilisearch on port 7700.
test with `curl -X GET 'http://localhost:7700/health'`
## Usage {#module-services-meilisearch-usage}
you first need to add documents to an index before you can search for documents.
### Add a documents to the `movies` index {#module-services-meilisearch-quickstart-add}
`curl -X POST 'http://127.0.0.1:7700/indexes/movies/documents' --data '[{"id": "123", "title": "Superman"}, {"id": 234, "title": "Batman"}]'`
### Search documents in the `movies` index {#module-services-meilisearch-quickstart-search}
`curl 'http://127.0.0.1:7700/indexes/movies/search' --data '{ "q": "botman" }'` (note the typo is intentional and there to demonstrate the typo tolerant capabilities)
## Defaults {#module-services-meilisearch-defaults}
- 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.
- `no_analytics` is set to true by default.
- `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.
- `db_path` is set to `/var/lib/meilisearch` by default. Upstream, the default value is equivalent to `/var/lib/meilisearch/data.ms`.
- `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

@@ -0,0 +1,278 @@
{
config,
lib,
pkgs,
...
}:
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;
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";
package = lib.mkPackageOption pkgs "meilisearch" {
extraDescription = ''
Use this if you require specific features to be enabled. The default package has no features.
'';
};
listenAddress = lib.mkOption {
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 {
default = 7700;
type = lib.types.port;
description = ''
The port that Meilisearch will listen on.
'';
};
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.
'';
default = null;
type = lib.types.nullOr lib.types.path;
};
settings = lib.mkOption {
description = ''
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 = { };
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;
};
};
};
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;
};
services.meilisearch.package = lib.mkDefault pkgs.meilisearch;
# used to restore dumps
environment.systemPackages = [ cfg.package ];
systemd.services.meilisearch = {
description = "Meilisearch daemon";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
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 = {
Type = "simple";
DynamicUser = true;
Restart = "always";
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";
StateDirectory = "meilisearch";
WorkingDirectory = "%S/meilisearch";
RuntimeDirectory = "meilisearch";
RuntimeDirectoryMode = "0700";
ReadWritePaths = [
cfg.settings.db_path
cfg.settings.dump_dir
cfg.settings.snapshot_dir
];
ProtectSystem = "strict";
ProtectHome = true;
ProtectClock = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
PrivateTmp = true;
PrivateMounts = true;
PrivateUsers = true;
PrivateDevices = true;
RestrictRealtime = true;
RestrictNamespaces = true;
RestrictSUIDSGID = true;
LockPersonality = true;
MemoryDenyWriteExecute = true;
RemoveIPC = true;
# Meilisearch needs to determine cgroup memory limits to set its own memory limits.
# This means this can't be set to "pid"
ProcSubset = "all";
ProtectProc = "invisible";
NoNewPrivileges = true;
# Meilisearch does not support listening on AF_UNIX sockets,
# so we currently restrict it to only AF_INET and AF_INET6.
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
];
CapabilityBoundingSet = "";
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged @resources"
];
UMask = "0077";
};
};
};
}

View File

@@ -0,0 +1,326 @@
{
lib,
config,
pkgs,
...
}:
let
cfg = config.services.nominatim;
localDb = cfg.database.host == "localhost";
uiPackage = cfg.ui.package.override { customConfig = cfg.ui.config; };
in
{
options.services.nominatim = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable nominatim.
Also enables nginx virtual host management. Further nginx configuration
can be done by adapting `services.nginx.virtualHosts.<name>`.
See [](#opt-services.nginx.virtualHosts).
'';
};
package = lib.mkPackageOption pkgs.python3Packages "nominatim-api" { };
hostName = lib.mkOption {
type = lib.types.str;
description = "Hostname to use for the nginx vhost.";
example = "nominatim.example.com";
};
settings = lib.mkOption {
default = { };
type = lib.types.attrsOf lib.types.str;
example = lib.literalExpression ''
{
NOMINATIM_REPLICATION_URL = "https://planet.openstreetmap.org/replication/minute";
NOMINATIM_REPLICATION_MAX_DIFF = "100";
}
'';
description = ''
Nominatim configuration settings.
For the list of available configuration options see
<https://nominatim.org/release-docs/latest/customize/Settings>.
'';
};
ui = {
package = lib.mkPackageOption pkgs "nominatim-ui" { };
config = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
Nominatim UI configuration placed to theme/config.theme.js file.
For the list of available configuration options see
<https://github.com/osm-search/nominatim-ui/blob/master/dist/config.defaults.js>.
'';
example = ''
Nominatim_Config.Page_Title='My Nominatim instance';
Nominatim_Config.Nominatim_API_Endpoint='https://localhost/';
'';
};
};
database = {
host = lib.mkOption {
type = lib.types.str;
default = "localhost";
description = ''
Host of the postgresql server. If not set to `localhost`, Nominatim
database and postgresql superuser with appropriate permissions must
exist on target host.
'';
};
port = lib.mkOption {
type = lib.types.port;
default = 5432;
description = "Port of the postgresql database.";
};
dbname = lib.mkOption {
type = lib.types.str;
default = "nominatim";
description = "Name of the postgresql database.";
};
superUser = lib.mkOption {
type = lib.types.str;
default = "nominatim";
description = ''
Postgresql database superuser used to create Nominatim database and
import data. If `database.host` is set to `localhost`, a unix user and
group of the same name will be automatically created.
'';
};
apiUser = lib.mkOption {
type = lib.types.str;
default = "nominatim-api";
description = ''
Postgresql database user with read-only permissions used for Nominatim
web API service.
'';
};
passwordFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = ''
Password file used for Nominatim database connection.
Must be readable only for the Nominatim web API user.
The file must be a valid `.pgpass` file as described in:
<https://www.postgresql.org/docs/current/libpq-pgpass.html>
In most cases, the following will be enough:
```
*:*:*:*:<password>
```
'';
};
extraConnectionParams = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
Extra Nominatim database connection parameters.
Format:
<param1>=<value1>;<param2>=<value2>
See <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS>.
'';
};
};
};
config =
let
nominatimSuperUserDsn =
"pgsql:dbname=${cfg.database.dbname};"
+ "user=${cfg.database.superUser}"
+ lib.optionalString (cfg.database.extraConnectionParams != null) (
";" + cfg.database.extraConnectionParams
);
nominatimApiDsn =
"pgsql:dbname=${cfg.database.dbname}"
+ lib.optionalString (!localDb) (
";host=${cfg.database.host};"
+ "port=${toString cfg.database.port};"
+ "user=${cfg.database.apiUser}"
)
+ lib.optionalString (cfg.database.extraConnectionParams != null) (
";" + cfg.database.extraConnectionParams
);
in
lib.mkIf cfg.enable {
# CLI package
environment.systemPackages = [ pkgs.nominatim ];
# Database
users.users.${cfg.database.superUser} = lib.mkIf localDb {
group = cfg.database.superUser;
isSystemUser = true;
createHome = false;
};
users.groups.${cfg.database.superUser} = lib.mkIf localDb { };
services.postgresql = lib.mkIf localDb {
enable = true;
extensions = ps: with ps; [ postgis ];
ensureUsers = [
{
name = cfg.database.superUser;
ensureClauses.superuser = true;
}
{
name = cfg.database.apiUser;
}
];
};
# TODO: add nominatim-update service
systemd.services.nominatim-init = lib.mkIf localDb {
after = [ "postgresql-setup.service" ];
requires = [ "postgresql-setup.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
User = cfg.database.superUser;
RemainAfterExit = true;
PrivateTmp = true;
};
script = ''
sql="SELECT COUNT(*) FROM pg_database WHERE datname='${cfg.database.dbname}'"
db_exists=$(${pkgs.postgresql}/bin/psql --dbname postgres -tAc "$sql")
if [ "$db_exists" == "0" ]; then
${lib.getExe pkgs.nominatim} import --prepare-database
else
echo "Database ${cfg.database.dbname} already exists. Skipping ..."
fi
'';
path = [
pkgs.postgresql
];
environment = {
NOMINATIM_DATABASE_DSN = nominatimSuperUserDsn;
NOMINATIM_DATABASE_WEBUSER = cfg.database.apiUser;
}
// cfg.settings;
};
# Web API service
users.users.${cfg.database.apiUser} = {
group = cfg.database.apiUser;
isSystemUser = true;
createHome = false;
};
users.groups.${cfg.database.apiUser} = { };
systemd.services.nominatim = {
after = [ "network.target" ] ++ lib.optionals localDb [ "nominatim-init.service" ];
requires = lib.optionals localDb [ "nominatim-init.service" ];
bindsTo = lib.optionals localDb [ "postgresql.service" ];
wantedBy = [ "multi-user.target" ];
wants = [ "network.target" ];
serviceConfig = {
Type = "simple";
User = cfg.database.apiUser;
ExecStart = ''
${pkgs.python3Packages.gunicorn}/bin/gunicorn \
--bind unix:/run/nominatim.sock \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker "nominatim_api.server.falcon.server:run_wsgi()"
'';
Environment = lib.optional (
cfg.database.passwordFile != null
) "PGPASSFILE=${cfg.database.passwordFile}";
ExecReload = "${pkgs.procps}/bin/kill -s HUP $MAINPID";
KillMode = "mixed";
TimeoutStopSec = 5;
};
environment = {
PYTHONPATH =
with pkgs.python3Packages;
pkgs.python3Packages.makePythonPath [
cfg.package
falcon
uvicorn
];
NOMINATIM_DATABASE_DSN = nominatimApiDsn;
NOMINATIM_DATABASE_WEBUSER = cfg.database.apiUser;
}
// cfg.settings;
};
systemd.sockets.nominatim = {
before = [ "nominatim.service" ];
wantedBy = [ "sockets.target" ];
socketConfig = {
ListenStream = "/run/nominatim.sock";
SocketUser = cfg.database.apiUser;
SocketGroup = config.services.nginx.group;
};
};
services.nginx = {
enable = true;
appendHttpConfig = ''
map $args $format {
default default;
~(^|&)format=html(&|$) html;
}
map $uri/$format $forward_to_ui {
default 0; # No forwarding by default.
# Redirect to HTML UI if explicitly requested.
~/reverse.*/html 1;
~/search.*/html 1;
~/lookup.*/html 1;
~/details.*/html 1;
}
'';
upstreams.nominatim = {
servers = {
"unix:/run/nominatim.sock" = { };
};
};
virtualHosts = {
${cfg.hostName} = {
forceSSL = lib.mkDefault true;
enableACME = lib.mkDefault true;
locations = {
"= /" = {
extraConfig = ''
return 301 $scheme://$http_host/ui/search.html;
'';
};
"/" = {
proxyPass = "http://nominatim";
extraConfig = ''
if ($forward_to_ui) {
rewrite ^(/[^/.]*) /ui$1.html redirect;
}
'';
};
"/ui/" = {
alias = "${uiPackage}/";
};
};
};
};
};
};
}

View File

@@ -0,0 +1,274 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.opensearch;
settingsFormat = pkgs.formats.yaml { };
configDir = cfg.dataDir + "/config";
usingDefaultDataDir = cfg.dataDir == "/var/lib/opensearch";
usingDefaultUserAndGroup = cfg.user == "opensearch" && cfg.group == "opensearch";
opensearchYml = settingsFormat.generate "opensearch.yml" cfg.settings;
loggingConfigFilename = "log4j2.properties";
loggingConfigFile = pkgs.writeTextFile {
name = loggingConfigFilename;
text = cfg.logging;
};
in
{
options.services.opensearch = {
enable = lib.mkEnableOption "OpenSearch";
package = lib.mkPackageOption pkgs "OpenSearch" {
default = [ "opensearch" ];
};
settings = lib.mkOption {
type = lib.types.submodule {
freeformType = settingsFormat.type;
options."network.host" = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = ''
Which port this service should listen on.
'';
};
options."cluster.name" = lib.mkOption {
type = lib.types.str;
default = "opensearch";
description = ''
The name of the cluster.
'';
};
options."discovery.type" = lib.mkOption {
type = lib.types.str;
default = "single-node";
description = ''
The type of discovery to use.
'';
};
options."http.port" = lib.mkOption {
type = lib.types.port;
default = 9200;
description = ''
The port to listen on for HTTP traffic.
'';
};
options."transport.port" = lib.mkOption {
type = lib.types.port;
default = 9300;
description = ''
The port to listen on for transport traffic.
'';
};
options."plugins.security.disabled" = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether to enable the security plugin,
`plugins.security.ssl.transport.keystore_filepath` or
`plugins.security.ssl.transport.server.pemcert_filepath` and
`plugins.security.ssl.transport.client.pemcert_filepath`
must be set for this plugin to be enabled.
'';
};
};
default = { };
description = ''
OpenSearch configuration.
'';
};
logging = lib.mkOption {
description = "opensearch logging configuration.";
default = ''
logger.action.name = org.opensearch.action
logger.action.level = info
appender.console.type = Console
appender.console.name = console
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
rootLogger.level = info
rootLogger.appenderRef.console.ref = console
'';
type = lib.types.str;
};
dataDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/opensearch";
apply = lib.converge (lib.removeSuffix "/");
description = ''
Data directory for OpenSearch. If you change this, you need to
manually create the directory. You also need to create the
`opensearch` user and group, or change
[](#opt-services.opensearch.user) and
[](#opt-services.opensearch.group) to existing ones with
access to the directory.
'';
};
user = lib.mkOption {
type = lib.types.str;
default = "opensearch";
description = ''
The user OpenSearch runs as. Should be left at default unless
you have very specific needs.
'';
};
group = lib.mkOption {
type = lib.types.str;
default = "opensearch";
description = ''
The group OpenSearch runs as. Should be left at default unless
you have very specific needs.
'';
};
extraCmdLineOptions = lib.mkOption {
description = "Extra command line options for the OpenSearch launcher.";
default = [ ];
type = lib.types.listOf lib.types.str;
};
extraJavaOptions = lib.mkOption {
description = "Extra command line options for Java.";
default = [ ];
type = lib.types.listOf lib.types.str;
example = [ "-Djava.net.preferIPv4Stack=true" ];
};
restartIfChanged = lib.mkOption {
type = lib.types.bool;
description = ''
Automatically restart the service on config change.
This can be set to false to defer restarts on a server or cluster.
Please consider the security implications of inadvertently running an older version,
and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
'';
default = true;
};
};
config = lib.mkIf cfg.enable {
systemd.services.opensearch = {
description = "OpenSearch Daemon";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
path = [ pkgs.inetutils ];
inherit (cfg) restartIfChanged;
environment = {
OPENSEARCH_HOME = cfg.dataDir;
OPENSEARCH_JAVA_OPTS = toString cfg.extraJavaOptions;
OPENSEARCH_PATH_CONF = configDir;
};
serviceConfig = {
ExecStartPre =
let
startPreFullPrivileges = ''
set -o errexit -o pipefail -o nounset -o errtrace
shopt -s inherit_errexit
''
+ (lib.optionalString (!config.boot.isContainer) ''
# Only set vm.max_map_count if lower than ES required minimum
# This avoids conflict if configured via boot.kernel.sysctl
if [ $(${pkgs.procps}/bin/sysctl -n vm.max_map_count) -lt 262144 ]; then
${pkgs.procps}/bin/sysctl -w vm.max_map_count=262144
fi
'');
startPreUnprivileged = ''
set -o errexit -o pipefail -o nounset -o errtrace
shopt -s inherit_errexit
# Install plugins
# remove plugins directory if it is empty.
if [[ -d ${cfg.dataDir}/plugins && -z "$(ls -A ${cfg.dataDir}/plugins)" ]]; then
rm -r "${cfg.dataDir}/plugins"
fi
ln -sfT "${cfg.package}/plugins" "${cfg.dataDir}/plugins"
ln -sfT ${cfg.package}/lib ${cfg.dataDir}/lib
ln -sfT ${cfg.package}/modules ${cfg.dataDir}/modules
# opensearch needs to create the opensearch.keystore in the config directory
# so this directory needs to be writable.
mkdir -p ${configDir}
chmod 0700 ${configDir}
# Note that we copy config files from the nix store instead of symbolically linking them
# because otherwise X-Pack Security will raise the following exception:
# java.security.AccessControlException:
# access denied ("java.io.FilePermission" "/var/lib/opensearch/config/opensearch.yml" "read")
rm -f ${configDir}/opensearch.yml
cp ${opensearchYml} ${configDir}/opensearch.yml
# Make sure the logging configuration for old OpenSearch versions is removed:
rm -f "${configDir}/logging.yml"
rm -f ${configDir}/${loggingConfigFilename}
cp ${loggingConfigFile} ${configDir}/${loggingConfigFilename}
mkdir -p ${configDir}/scripts
rm -f ${configDir}/jvm.options
cp ${cfg.package}/config/jvm.options ${configDir}/jvm.options
# redirect jvm logs to the data directory
mkdir -p ${cfg.dataDir}/logs
chmod 0700 ${cfg.dataDir}/logs
sed -e '#logs/gc.log#${cfg.dataDir}/logs/gc.log#' -i ${configDir}/jvm.options
'';
in
[
"+${pkgs.writeShellScript "opensearch-start-pre-full-privileges" startPreFullPrivileges}"
"${pkgs.writeShellScript "opensearch-start-pre-unprivileged" startPreUnprivileged}"
];
ExecStartPost = pkgs.writeShellScript "opensearch-start-post" ''
set -o errexit -o pipefail -o nounset -o errtrace
shopt -s inherit_errexit
# Make sure opensearch is up and running before dependents
# are started
while ! ${pkgs.curl}/bin/curl -sS -f http://${cfg.settings."network.host"}:${
toString cfg.settings."http.port"
} 2>/dev/null; do
sleep 1
done
'';
ExecStart = "${cfg.package}/bin/opensearch ${toString cfg.extraCmdLineOptions}";
User = cfg.user;
Group = cfg.group;
LimitNOFILE = "1024000";
Restart = "always";
TimeoutStartSec = "infinity";
DynamicUser = usingDefaultUserAndGroup && usingDefaultDataDir;
}
// (lib.optionalAttrs usingDefaultDataDir {
StateDirectory = "opensearch";
StateDirectoryMode = "0700";
});
};
environment.systemPackages = [ cfg.package ];
};
}

View File

@@ -0,0 +1,141 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.qdrant;
settingsFormat = pkgs.formats.yaml { };
configFile = settingsFormat.generate "config.yaml" cfg.settings;
in
{
options = {
services.qdrant = {
enable = lib.mkEnableOption "Vector Search Engine for the next generation of AI applications";
package = lib.mkPackageOption pkgs "qdrant" { };
webUIPackage = lib.mkPackageOption pkgs "qdrant-web-ui" { };
settings = lib.mkOption {
description = ''
Configuration for Qdrant
Refer to <https://github.com/qdrant/qdrant/blob/master/config/config.yaml> for details on supported values.
'';
type = settingsFormat.type;
example = {
storage = {
storage_path = "/var/lib/qdrant/storage";
snapshots_path = "/var/lib/qdrant/snapshots";
};
hsnw_index = {
on_disk = true;
};
service = {
host = "127.0.0.1";
http_port = 6333;
grpc_port = 6334;
};
telemetry_disabled = true;
};
defaultText = lib.literalExpression ''
{
storage = {
storage_path = "/var/lib/qdrant/storage";
snapshots_path = "/var/lib/qdrant/snapshots";
};
hsnw_index = {
on_disk = true;
};
service = {
host = "127.0.0.1";
http_port = 6333;
grpc_port = 6334;
};
telemetry_disabled = true;
}
'';
};
};
};
config = lib.mkIf cfg.enable {
services.qdrant.settings = {
service.static_content_dir = lib.mkDefault cfg.webUIPackage;
storage.storage_path = lib.mkDefault "/var/lib/qdrant/storage";
storage.snapshots_path = lib.mkDefault "/var/lib/qdrant/snapshots";
# The following default values are the same as in the default config,
# they are just written here for convenience.
storage.on_disk_payload = lib.mkDefault true;
storage.wal.wal_capacity_mb = lib.mkDefault 32;
storage.wal.wal_segments_ahead = lib.mkDefault 0;
storage.performance.max_search_threads = lib.mkDefault 0;
storage.performance.max_optimization_threads = lib.mkDefault 1;
storage.optimizers.deleted_threshold = lib.mkDefault 0.2;
storage.optimizers.vacuum_min_vector_number = lib.mkDefault 1000;
storage.optimizers.default_segment_number = lib.mkDefault 0;
storage.optimizers.max_segment_size_kb = lib.mkDefault null;
storage.optimizers.memmap_threshold_kb = lib.mkDefault null;
storage.optimizers.indexing_threshold_kb = lib.mkDefault 20000;
storage.optimizers.flush_interval_sec = lib.mkDefault 5;
storage.optimizers.max_optimization_threads = lib.mkDefault 1;
storage.hnsw_index.m = lib.mkDefault 16;
storage.hnsw_index.ef_construct = lib.mkDefault 100;
storage.hnsw_index.full_scan_threshold_kb = lib.mkDefault 10000;
storage.hnsw_index.max_indexing_threads = lib.mkDefault 0;
storage.hnsw_index.on_disk = lib.mkDefault false;
storage.hnsw_index.payload_m = lib.mkDefault null;
service.max_request_size_mb = lib.mkDefault 32;
service.max_workers = lib.mkDefault 0;
service.http_port = lib.mkDefault 6333;
service.grpc_port = lib.mkDefault 6334;
service.enable_cors = lib.mkDefault true;
cluster.enabled = lib.mkDefault false;
# the following have been altered for security
service.host = lib.mkDefault "127.0.0.1";
telemetry_disabled = lib.mkDefault true;
};
systemd.services.qdrant = {
description = "Vector Search Engine for the next generation of AI applications";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
LimitNOFILE = 65536;
ExecStart = "${cfg.package}/bin/qdrant --config-path ${configFile}";
DynamicUser = true;
Restart = "on-failure";
StateDirectory = "qdrant";
CapabilityBoundingSet = "";
NoNewPrivileges = true;
PrivateTmp = true;
ProtectHome = true;
ProtectClock = true;
ProtectProc = "noaccess";
ProcSubset = "pid";
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectHostname = true;
RestrictSUIDSGID = true;
RestrictRealtime = true;
RestrictNamespaces = true;
LockPersonality = true;
RemoveIPC = true;
SystemCallFilter = [
"@system-service"
"~@privileged"
];
};
};
};
}

View File

@@ -0,0 +1,193 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.quickwit;
settingsFormat = pkgs.formats.yaml { };
quickwitYml = settingsFormat.generate "quickwit.yml" cfg.settings;
usingDefaultDataDir = cfg.dataDir == "/var/lib/quickwit";
usingDefaultUserAndGroup = cfg.user == "quickwit" && cfg.group == "quickwit";
in
{
options.services.quickwit = {
enable = lib.mkEnableOption "Quickwit";
package = lib.mkPackageOption pkgs "Quickwit" {
default = [ "quickwit" ];
};
settings = lib.mkOption {
type = lib.types.submodule {
freeformType = settingsFormat.type;
options."rest" = lib.mkOption {
default = { };
description = ''
Rest server configuration for Quickwit
'';
type = lib.types.submodule {
freeformType = settingsFormat.type;
options."listen_port" = lib.mkOption {
type = lib.types.port;
default = 7280;
description = ''
The port to listen on for HTTP REST traffic.
'';
};
};
};
options."grpc_listen_port" = lib.mkOption {
type = lib.types.port;
default = 7281;
description = ''
The port to listen on for gRPC traffic.
'';
};
options."listen_address" = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = ''
Listen address of Quickwit.
'';
};
options."version" = lib.mkOption {
type = lib.types.float;
default = 0.7;
description = ''
Configuration file version.
'';
};
};
default = { };
description = ''
Quickwit configuration.
'';
};
dataDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/quickwit";
apply = lib.converge (lib.removeSuffix "/");
description = ''
Data directory for Quickwit. If you change this, you need to
manually create the directory. You also need to create the
`quickwit` user and group, or change
[](#opt-services.quickwit.user) and
[](#opt-services.quickwit.group) to existing ones with
access to the directory.
'';
};
user = lib.mkOption {
type = lib.types.str;
default = "quickwit";
description = ''
The user Quickwit runs as. Should be left at default unless
you have very specific needs.
'';
};
group = lib.mkOption {
type = lib.types.str;
default = "quickwit";
description = ''
The group quickwit runs as. Should be left at default unless
you have very specific needs.
'';
};
extraFlags = lib.mkOption {
description = "Extra command line options to pass to Quickwit.";
default = [ ];
type = lib.types.listOf lib.types.str;
};
restartIfChanged = lib.mkOption {
type = lib.types.bool;
description = ''
Automatically restart the service on config change.
This can be set to false to defer restarts on a server or cluster.
Please consider the security implications of inadvertently running an older version,
and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
'';
default = true;
};
};
config = lib.mkIf cfg.enable {
systemd.services.quickwit = {
description = "Quickwit";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
inherit (cfg) restartIfChanged;
environment = {
QW_DATA_DIR = cfg.dataDir;
};
serviceConfig = {
ExecStart = ''
${cfg.package}/bin/quickwit run --config ${quickwitYml} \
${lib.escapeShellArgs cfg.extraFlags}
'';
User = cfg.user;
Group = cfg.group;
Restart = "on-failure";
DynamicUser = usingDefaultUserAndGroup && usingDefaultDataDir;
CapabilityBoundingSet = [ "" ];
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectHome = true;
ProtectHostname = true;
ProtectControlGroups = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
ReadWritePaths = [
cfg.dataDir
];
RestrictAddressFamilies = [
"AF_NETLINK"
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
# 1. allow a reasonable set of syscalls
"@system-service @resources"
# 2. and deny unreasonable ones
"~@privileged"
# 3. then allow the required subset within denied groups
"@chown"
];
}
// (lib.optionalAttrs usingDefaultDataDir {
StateDirectory = "quickwit";
StateDirectoryMode = "0700";
});
};
environment.systemPackages = [ cfg.package ];
};
}

View File

@@ -0,0 +1,83 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.sonic-server;
settingsFormat = pkgs.formats.toml { };
configFile = settingsFormat.generate "sonic-server-config.toml" cfg.settings;
in
{
meta.maintainers = [ lib.maintainers.anthonyroussel ];
options = {
services.sonic-server = {
enable = lib.mkEnableOption "Sonic Search Index";
package = lib.mkPackageOption pkgs "sonic-server" { };
settings = lib.mkOption {
type = lib.types.submodule { freeformType = settingsFormat.type; };
default = {
store.kv.path = "/var/lib/sonic/kv";
store.fst.path = "/var/lib/sonic/fst";
};
example = {
server.log_level = "debug";
channel.inet = "[::1]:1491";
};
description = ''
Sonic Server configuration options.
Refer to
<https://github.com/valeriansaliou/sonic/blob/master/CONFIGURATION.md>
for a full list of available options.
'';
};
};
};
config = lib.mkIf cfg.enable {
services.sonic-server.settings = lib.mapAttrs (name: lib.mkDefault) {
server = { };
channel.search = { };
store = {
kv = {
path = "/var/lib/sonic/kv";
database = { };
pool = { };
};
fst = {
path = "/var/lib/sonic/fst";
graph = { };
pool = { };
};
};
};
systemd.services.sonic-server = {
description = "Sonic Search Index";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
Type = "simple";
ExecStart = "${lib.getExe cfg.package} -c ${configFile}";
DynamicUser = true;
Group = "sonic";
LimitNOFILE = "infinity";
Restart = "on-failure";
StateDirectory = "sonic";
StateDirectoryMode = "750";
User = "sonic";
};
};
};
}

View File

@@ -0,0 +1,101 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.tika;
inherit (lib)
literalExpression
mkIf
mkEnableOption
mkOption
mkPackageOption
getExe
types
;
in
{
meta.maintainers = [ ];
options = {
services.tika = {
enable = mkEnableOption "Apache Tika server";
package = mkPackageOption pkgs "tika" { };
listenAddress = mkOption {
type = types.str;
default = "127.0.0.1";
example = "0.0.0.0";
description = ''
The Apache Tika bind address.
'';
};
port = mkOption {
type = types.port;
default = 9998;
description = ''
The Apache Tike port to listen on
'';
};
configFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
The Apache Tika configuration (XML) file to use.
'';
example = literalExpression "./tika/tika-config.xml";
};
enableOcr = mkOption {
type = types.bool;
default = true;
description = ''
Whether to enable OCR support by adding the `tesseract` package as a dependency.
'';
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = ''
Whether to open the firewall for Apache Tika.
This adds `services.tika.port` to `networking.firewall.allowedTCPPorts`.
'';
};
};
};
config = mkIf cfg.enable {
systemd.services.tika = {
description = "Apache Tika Server";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig =
let
package = cfg.package.override {
inherit (cfg) enableOcr;
enableGui = false;
};
in
{
Type = "simple";
ExecStart = "${getExe package} --host ${cfg.listenAddress} --port ${toString cfg.port} ${
lib.optionalString (cfg.configFile != null) "--config ${cfg.configFile}"
}";
DynamicUser = true;
StateDirectory = "tika";
CacheDirectory = "tika";
};
};
networking.firewall = mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.port ]; };
};
}

View File

@@ -0,0 +1,126 @@
{
config,
lib,
pkgs,
...
}:
let
inherit (lib)
concatMapStringsSep
generators
mkEnableOption
mkIf
mkOption
mkPackageOption
optionalString
types
;
cfg = config.services.typesense;
settingsFormatIni = pkgs.formats.ini {
listToValue = concatMapStringsSep " " (generators.mkValueStringDefault { });
mkKeyValue = generators.mkKeyValueDefault {
mkValueString = v: if v == null then "" else generators.mkValueStringDefault { } v;
} "=";
};
configFile = settingsFormatIni.generate "typesense.ini" cfg.settings;
in
{
options.services.typesense = {
enable = mkEnableOption "typesense";
package = mkPackageOption pkgs "typesense" { };
apiKeyFile = mkOption {
type = types.path;
description = ''
Sets the admin api key for typesense. Always use this option
instead of {option}`settings.server.api-key` to prevent the key
from being written to the world-readable nix store.
'';
};
settings = mkOption {
description = "Typesense configuration. Refer to [the documentation](https://typesense.org/docs/0.24.1/api/server-configuration.html) for supported values.";
default = { };
type = types.submodule {
freeformType = settingsFormatIni.type;
options.server = {
data-dir = mkOption {
type = types.str;
default = "/var/lib/typesense";
description = "Path to the directory where data will be stored on disk.";
};
api-address = mkOption {
type = types.str;
description = "Address to which Typesense API service binds.";
};
api-port = mkOption {
type = types.port;
default = 8108;
description = "Port on which the Typesense API service listens.";
};
};
};
};
};
config = mkIf cfg.enable {
systemd.services.typesense = {
description = "Typesense search engine";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
script = ''
export TYPESENSE_API_KEY=$(cat ${cfg.apiKeyFile})
exec ${cfg.package}/bin/typesense-server --config ${configFile}
'';
serviceConfig = {
Restart = "on-failure";
DynamicUser = true;
User = "typesense";
Group = "typesense";
StateDirectory = "typesense";
StateDirectoryMode = "0750";
# Hardening
CapabilityBoundingSet = "";
LockPersonality = true;
# MemoryDenyWriteExecute = true; needed since 0.25.1
NoNewPrivileges = true;
PrivateUsers = true;
PrivateTmp = true;
PrivateDevices = true;
PrivateMounts = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProcSubset = "pid";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
];
UMask = "0077";
};
};
};
}