nixos/kiwix-serve: init module (#496047)

This commit is contained in:
Colin 2026-03-06 19:13:00 +00:00 committed by GitHub
commit d0a37ae396
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 280 additions and 0 deletions

View file

@ -30,6 +30,8 @@
- [qui](https://github.com/autobrr/qui), a modern alternative webUI for qBittorrent, with multi-instance support. Written in Go/React. Available as [services.qui](#opt-services.qui.enable).
- [kiwix-serve](https://wiki.kiwix.org/wiki/Kiwix-serve), a service that serves ZIM files (such as Wikipedia archives) over HTTP. Available as [services.kiwix-serve](#opt-services.kiwix-serve.enable).
- [Remark42](https://remark42.com/), a self-hosted comment engine. Available as [services.remark42](#opt-services.remark42.enable).
- [LibreChat](https://www.librechat.ai/), open-source self-hostable ChatGPT clone with Agents and RAG APIs. Available as [services.librechat](#opt-services.librechat.enable).

View file

@ -882,6 +882,7 @@
./services/misc/jackett.nix
./services/misc/jellyfin.nix
./services/misc/jellyseerr.nix
./services/misc/kiwix-serve.nix
./services/misc/klipper.nix
./services/misc/languagetool.nix
./services/misc/leaps.nix

View file

@ -0,0 +1,187 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
inherit (lib) types;
cfg = config.services.kiwix-serve;
# Create a directory containing symlinks to ZIM files
mkLibrary =
library:
let
libraryEntries = lib.mapAttrsToList (name: path: {
name = "${name}.zim";
inherit path;
}) library;
zimsDrv = pkgs.linkFarm "zims" libraryEntries;
files = map (entry: "${zimsDrv}/${entry.name}") libraryEntries;
in
{
derivation = zimsDrv;
inherit files;
};
in
{
options = {
services.kiwix-serve = {
enable = lib.mkEnableOption "the kiwix-serve server";
package = lib.mkPackageOption pkgs "kiwix-tools" { };
address = lib.mkOption {
type = types.str;
default = "all";
example = "ipv4";
description = ''
Listen only on the specified IP address.
Specify "ipv4", "ipv6" or "all" to listen on all IPv4, IPv6, or both types of addresses, respectively.
'';
};
port = lib.mkOption {
type = types.port;
default = 8080;
description = "The port on which to run kiwix-serve.";
};
openFirewall = lib.mkOption {
type = types.bool;
default = false;
description = "Whether to open the firewall for the configured port.";
};
library = lib.mkOption {
type = types.attrsOf types.path;
default = { };
example = lib.literalExpression (
lib.removeSuffix "\n" ''
{
wikipedia = "/data/wikipedia_en_all_maxi_2026-02.zim";
nix = pkgs.fetchurl {
url = "https://download.kiwix.org/zim/devdocs/devdocs_en_nix_2026-01.zim";
hash = "sha256-QxB9qDKSzzEU8t4droI08BXdYn+HMVkgiJMO3SoGTqM=";
};
}
''
);
description = ''
A set of ZIM files to serve. The key is used as the name for the ZIM files
(e.g. in the example, the files will be served as `wikipedia.zim` and `nix.zim`).
Exclusive with [services.kiwix-serve.libraryPath](#opt-services.kiwix-serve.libraryPath).
'';
};
libraryPath = lib.mkOption {
type = types.nullOr types.path;
default = null;
example = "/data/library.xml";
description = ''
An XML library file listing ZIM files to serve.
For more information, see <https://wiki.kiwix.org/wiki/Kiwix-manage>.
Exclusive with [services.kiwix-serve.library](#opt-services.kiwix-serve.library).
'';
};
extraArgs = lib.mkOption {
type = types.listOf types.str;
default = [ ];
example = [
"--verbose"
"--skipInvalid"
];
description = "Extra arguments to pass to kiwix-serve.";
};
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = (cfg.library == { }) != (cfg.libraryPath == null);
message = "Exactly one of services.kiwix-serve.library or services.kiwix-serve.libraryPath must be provided.";
}
];
systemd.services.kiwix-serve =
let
library = mkLibrary cfg.library;
in
{
description = "ZIM file HTTP server";
documentation = [ "https://kiwix-tools.readthedocs.io/en/latest/kiwix-serve.html" ];
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "exec";
DynamicUser = true;
Restart = "on-failure";
ExecStart = utils.escapeSystemdExecArgs (
[
(lib.getExe' cfg.package "kiwix-serve")
"--address"
cfg.address
"--port"
cfg.port
]
++ lib.optionals (cfg.libraryPath != null) [
"--library"
cfg.libraryPath
]
++ lib.optionals (cfg.library != { }) library.files
++ cfg.extraArgs
);
CapabilityBoundingSet = "";
DeviceAllow = "";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateUsers = true;
PrivateTmp = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
"~@resources"
];
UMask = "0077";
};
};
networking.firewall = lib.mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
};
};
meta = {
maintainers = with lib.maintainers; [ MysteryBlokHed ];
};
}

View file

@ -840,6 +840,7 @@ in
keymap = handleTest ./keymap.nix { };
kimai = runTest ./kimai.nix;
kismet = runTest ./kismet.nix;
kiwix-serve = runTest ./kiwix-serve;
kmonad = runTest ./kmonad.nix;
kmscon = runTest ./kmscon.nix;
knot = runTest ./knot.nix;

View file

@ -0,0 +1,75 @@
{ lib, pkgs, ... }:
let
mkTestZim =
name:
pkgs.runCommandLocal "${name}.zim"
{
nativeBuildInputs = [ pkgs.zim-tools ];
}
''
${lib.getExe' pkgs.zim-tools "zimwriterfs"} \
--name "${name}" \
--title 'NixOS kiwix-serve Test' \
--description 'NixOS test of kiwix-serve' \
--creator Nixpkgs \
--publisher Nixpkgs \
--language eng \
--welcome index.html \
--illustration icon.png \
${./html} \
$out
'';
# Test files must have different names or kiwix-serve will only serve one of them
testZimStore = mkTestZim "test-store";
testZimOutside = mkTestZim "test-outside";
in
{
name = "kiwix-serve";
meta.maintainers = with lib.maintainers; [ MysteryBlokHed ];
nodes = {
machine = {
systemd.services.copy-zim-file = {
description = "Copy test ZIM file to host system to test paths outside of store";
wantedBy = [ "multi-user.target" ];
before = [ "kiwix-serve.service" ];
requiredBy = [ "kiwix-serve.service" ];
serviceConfig = {
Type = "oneshot";
};
script = ''
mkdir -p /var/lib/kiwix-serve
cp ${testZimOutside} /var/lib/kiwix-serve/test-outside.zim
'';
};
services.kiwix-serve = {
enable = true;
port = 8080;
library = {
test-store = testZimStore;
test-outside = "/var/lib/kiwix-serve/test-outside.zim";
};
};
};
};
testScript = ''
machine.wait_for_unit("kiwix-serve.service")
machine.wait_for_open_port(8080)
machine.wait_until_succeeds("curl --fail --silent --head http://localhost:8080")
# ZIM file in store
test_content = machine.succeed("curl --fail --silent --location http://localhost:8080/content/test-store")
print(test_content)
assert "NixOS test of kiwix-serve" in test_content, "kiwix-serve did not provide the expected page for the store ZIM file"
# ZIM file outside of store
test_content = machine.succeed("curl --fail --silent --location http://localhost:8080/content/test-outside")
print(test_content)
assert "NixOS test of kiwix-serve" in test_content, "kiwix-serve did not provide the expected page for the out-of-store ZIM file"
'';
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 B

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>NixOS kiwix-serve Test</title>
</head>
<body>
<h1>NixOS test of kiwix-serve</h1>
</body>
</html>

View file

@ -3,6 +3,7 @@
docopt_cpp,
fetchFromGitHub,
gitUpdater,
nixosTests,
icu,
libkiwix,
meson,
@ -34,6 +35,8 @@ stdenv.mkDerivation (finalAttrs: {
libkiwix
];
passthru.tests.kiwix-serve = nixosTests.kiwix-serve;
passthru.updateScript = gitUpdater { };
meta = {