diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 4a63a09ab846..6524cc62bb75 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -924,6 +924,7 @@
./services/web-apps/selfoss.nix
./services/web-apps/shiori.nix
./services/web-apps/virtlyst.nix
+ ./services/web-apps/wiki-js.nix
./services/web-apps/whitebophir.nix
./services/web-apps/wordpress.nix
./services/web-apps/youtrack.nix
diff --git a/nixos/modules/services/web-apps/wiki-js.nix b/nixos/modules/services/web-apps/wiki-js.nix
new file mode 100644
index 000000000000..1a6259dffeef
--- /dev/null
+++ b/nixos/modules/services/web-apps/wiki-js.nix
@@ -0,0 +1,139 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+let
+ cfg = config.services.wiki-js;
+
+ format = pkgs.formats.json { };
+
+ configFile = format.generate "wiki-js.yml" cfg.settings;
+in {
+ options.services.wiki-js = {
+ enable = mkEnableOption "wiki-js";
+
+ environmentFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/root/wiki-js.env";
+ description = ''
+ Environment fiel to inject e.g. secrets into the configuration.
+ '';
+ };
+
+ stateDirectoryName = mkOption {
+ default = "wiki-js";
+ type = types.str;
+ description = ''
+ Name of the directory in /var/lib.
+ '';
+ };
+
+ settings = mkOption {
+ default = {};
+ type = types.submodule {
+ freeformType = format.type;
+ options = {
+ port = mkOption {
+ type = types.port;
+ default = 3000;
+ description = ''
+ TCP port the process should listen to.
+ '';
+ };
+
+ bindIP = mkOption {
+ default = "0.0.0.0";
+ type = types.str;
+ description = ''
+ IPs the service should listen to.
+ '';
+ };
+
+ db = {
+ type = mkOption {
+ default = "postgres";
+ type = types.enum [ "postgres" "mysql" "mariadb" "mssql" ];
+ description = ''
+ Database driver to use for persistence. Please note that sqlite
+ is currently not supported as the build process for it is currently not implemented
+ in pkgs.wiki-js and it's not recommended by upstream for
+ production use.
+ '';
+ };
+ host = mkOption {
+ type = types.str;
+ example = "/run/postgresql";
+ description = ''
+ Hostname or socket-path to connect to.
+ '';
+ };
+ db = mkOption {
+ default = "wiki";
+ type = types.str;
+ description = ''
+ Name of the database to use.
+ '';
+ };
+ };
+
+ logLevel = mkOption {
+ default = "info";
+ type = types.enum [ "error" "warn" "info" "verbose" "debug" "silly" ];
+ description = ''
+ Define how much detail is supposed to be logged at runtime.
+ '';
+ };
+
+ offline = mkEnableOption "offline mode" // {
+ description = ''
+ Disable latest file updates and enable
+ sideloading.
+ '';
+ };
+ };
+ };
+ description = ''
+ Settings to configure wiki-js. This directly
+ corresponds to the upstream
+ configuration options.
+
+ Secrets can be injected via the environment by
+
+ specifying
+ to contain secrets
+ and setting sensitive values to $(ENVIRONMENT_VAR)
+ with this value defined in the environment-file.
+
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.wiki-js.settings.dataPath = "/var/lib/${cfg.stateDirectoryName}";
+ systemd.services.wiki-js = {
+ description = "A modern and powerful wiki app built on Node.js";
+ documentation = [ "https://docs.requarks.io/" ];
+ wantedBy = [ "multi-user.target" ];
+
+ path = with pkgs; [ coreutils ];
+ preStart = ''
+ ln -sf ${configFile} /var/lib/${cfg.stateDirectoryName}/config.yml
+ ln -sf ${pkgs.wiki-js}/server /var/lib/${cfg.stateDirectoryName}
+ ln -sf ${pkgs.wiki-js}/assets /var/lib/${cfg.stateDirectoryName}
+ ln -sf ${pkgs.wiki-js}/package.json /var/lib/${cfg.stateDirectoryName}/package.json
+ '';
+
+ serviceConfig = {
+ EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
+ StateDirectory = cfg.stateDirectoryName;
+ WorkingDirectory = "/var/lib/${cfg.stateDirectoryName}";
+ DynamicUser = true;
+ PrivateTmp = true;
+ ExecStart = "${pkgs.nodejs}/bin/node ${pkgs.wiki-js}/server";
+ };
+ };
+ };
+
+ meta.maintainers = with maintainers; [ ma27 ];
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index fb45ec1a310c..65c7d84ee644 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -426,6 +426,7 @@ in
virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {};
vscodium = handleTest ./vscodium.nix {};
wasabibackend = handleTest ./wasabibackend.nix {};
+ wiki-js = handleTest ./wiki-js.nix {};
wireguard = handleTest ./wireguard {};
wordpress = handleTest ./wordpress.nix {};
xandikos = handleTest ./xandikos.nix {};
diff --git a/nixos/tests/wiki-js.nix b/nixos/tests/wiki-js.nix
new file mode 100644
index 000000000000..9aa87d15366b
--- /dev/null
+++ b/nixos/tests/wiki-js.nix
@@ -0,0 +1,152 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+ name = "wiki-js";
+ meta = with pkgs.lib.maintainers; {
+ maintainers = [ ma27 ];
+ };
+
+ machine = { pkgs, ... }: {
+ virtualisation.memorySize = 2048;
+ services.wiki-js = {
+ enable = true;
+ settings.db.host = "/run/postgresql";
+ settings.db.user = "wiki-js";
+ settings.logLevel = "debug";
+ };
+ services.postgresql = {
+ enable = true;
+ ensureDatabases = [ "wiki" ];
+ ensureUsers = [
+ { name = "wiki-js";
+ ensurePermissions."DATABASE wiki" = "ALL PRIVILEGES";
+ }
+ ];
+ };
+ systemd.services.wiki-js = {
+ requires = [ "postgresql.service" ];
+ after = [ "postgresql.service" ];
+ };
+ environment.systemPackages = with pkgs; [ jq ];
+ };
+
+ testScript = let
+ payloads.finalize = pkgs.writeText "finalize.json" (builtins.toJSON {
+ adminEmail = "webmaster@example.com";
+ adminPassword = "notapassword";
+ adminPasswordConfirm = "notapassword";
+ siteUrl = "http://localhost:3000";
+ telemetry = false;
+ });
+ payloads.login = pkgs.writeText "login.json" (builtins.toJSON [{
+ operationName = null;
+ extensions = {};
+ query = ''
+ mutation ($username: String!, $password: String!, $strategy: String!) {
+ authentication {
+ login(username: $username, password: $password, strategy: $strategy) {
+ responseResult {
+ succeeded
+ errorCode
+ slug
+ message
+ __typename
+ }
+ jwt
+ mustChangePwd
+ mustProvideTFA
+ mustSetupTFA
+ continuationToken
+ redirect
+ tfaQRImage
+ __typename
+ }
+ __typename
+ }
+ }
+ '';
+ variables = {
+ password = "notapassword";
+ strategy = "local";
+ username = "webmaster@example.com";
+ };
+ }]);
+ payloads.content = pkgs.writeText "content.json" (builtins.toJSON [{
+ extensions = {};
+ operationName = null;
+ query = ''
+ mutation ($content: String!, $description: String!, $editor: String!, $isPrivate: Boolean!, $isPublished: Boolean!, $locale: String!, $path: String!, $publishEndDate: Date, $publishStartDate: Date, $scriptCss: String, $scriptJs: String, $tags: [String]!, $title: String!) {
+ pages {
+ create(content: $content, description: $description, editor: $editor, isPrivate: $isPrivate, isPublished: $isPublished, locale: $locale, path: $path, publishEndDate: $publishEndDate, publishStartDate: $publishStartDate, scriptCss: $scriptCss, scriptJs: $scriptJs, tags: $tags, title: $title) {
+ responseResult {
+ succeeded
+ errorCode
+ slug
+ message
+ __typename
+ }
+ page {
+ id
+ updatedAt
+ __typename
+ }
+ __typename
+ }
+ __typename
+ }
+ }
+ '';
+ variables = {
+ content = "# Header\n\nHello world!";
+ description = "";
+ editor = "markdown";
+ isPrivate = false;
+ isPublished = true;
+ locale = "en";
+ path = "home";
+ publishEndDate = "";
+ publishStartDate = "";
+ scriptCss = "";
+ scriptJs = "";
+ tags = [];
+ title = "Hello world";
+ };
+ }]);
+ in ''
+ machine.start()
+ machine.wait_for_unit("multi-user.target")
+ machine.wait_for_open_port(3000)
+
+ machine.succeed("curl -sSf localhost:3000")
+
+ with subtest("Setup"):
+ result = machine.succeed(
+ "set -o pipefail; curl -sSf localhost:3000/finalize -X POST -d "
+ + "@${payloads.finalize} -H 'Content-Type: application/json' "
+ + "| jq .ok | xargs echo"
+ )
+ assert result.strip() == "true", f"Expected true, got {result}"
+
+ # During the setup the service gets restarted, so we use this
+ # to check if the setup is done.
+ machine.wait_until_fails("curl -sSf localhost:3000")
+ machine.wait_until_succeeds("curl -sSf localhost:3000")
+
+ with subtest("Base functionality"):
+ auth = machine.succeed(
+ "set -o pipefail; curl -sSf localhost:3000/graphql -X POST "
+ + "-d @${payloads.login} -H 'Content-Type: application/json' "
+ + "| jq '.[0].data.authentication.login.jwt' | xargs echo"
+ ).strip()
+
+ assert auth
+
+ create = machine.succeed(
+ "set -o pipefail; curl -sSf localhost:3000/graphql -X POST "
+ + "-d @${payloads.content} -H 'Content-Type: application/json' "
+ + f"-H 'Authorization: Bearer {auth}' "
+ + "| jq '.[0].data.pages.create.responseResult.succeeded'|xargs echo"
+ )
+ assert create.strip() == "true", f"Expected true, got {create}"
+
+ machine.shutdown()
+ '';
+})
diff --git a/pkgs/servers/web-apps/wiki-js/default.nix b/pkgs/servers/web-apps/wiki-js/default.nix
new file mode 100644
index 000000000000..974b7a62e50c
--- /dev/null
+++ b/pkgs/servers/web-apps/wiki-js/default.nix
@@ -0,0 +1,32 @@
+{ stdenv, fetchurl, lib, nixosTests }:
+
+stdenv.mkDerivation rec {
+ pname = "wiki-js";
+ version = "2.5.197";
+
+ src = fetchurl {
+ url = "https://github.com/Requarks/wiki/releases/download/${version}/${pname}.tar.gz";
+ sha256 = "sha256-0xM9BtQvSt5WkbKBri+KxB+Ghc4wgY8/TUgI6PCFmm0=";
+ };
+
+ sourceRoot = ".";
+
+ dontBuild = true;
+ installPhase = ''
+ runHook preInstall
+
+ mkdir $out
+ cp -r . $out
+
+ runHook postInstall
+ '';
+
+ passthru.tests = { inherit (nixosTests) wiki-js; };
+
+ meta = with lib; {
+ homepage = "https://js.wiki/";
+ description = "A modern and powerful wiki app built on Node.js";
+ license = licenses.agpl3Only;
+ maintainers = with maintainers; [ ma27 ];
+ };
+}
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index 68ede4a7ef74..d3d77cdf132f 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -30076,6 +30076,8 @@ in
pythonPackages = python3Packages;
};
+ wiki-js = callPackage ../servers/web-apps/wiki-js { };
+
winePackagesFor = wineBuild: lib.makeExtensible (self: with self; {
callPackage = newScope self;