From e86e3e0fc974084670aa5accfd84174a38308295 Mon Sep 17 00:00:00 2001 From: Victor Engmark Date: Sat, 19 Oct 2024 21:42:48 +1300 Subject: [PATCH] nixos/linkding: init Closes #341665. Co-Authored-By: Pol Dellaiera --- .../manual/release-notes/rl-2505.section.md | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/services/web-apps/linkding.nix | 266 ++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/web-apps/linkding/default.nix | 5 + nixos/tests/web-apps/linkding/defaults.nix | 20 ++ nixos/tests/web-apps/linkding/overrides.nix | 33 +++ 7 files changed, 328 insertions(+) create mode 100644 nixos/modules/services/web-apps/linkding.nix create mode 100644 nixos/tests/web-apps/linkding/default.nix create mode 100644 nixos/tests/web-apps/linkding/defaults.nix create mode 100644 nixos/tests/web-apps/linkding/overrides.nix diff --git a/nixos/doc/manual/release-notes/rl-2505.section.md b/nixos/doc/manual/release-notes/rl-2505.section.md index 1fbd5aad9230fb..4b6ccb28e05b7a 100644 --- a/nixos/doc/manual/release-notes/rl-2505.section.md +++ b/nixos/doc/manual/release-notes/rl-2505.section.md @@ -45,6 +45,8 @@ - [Omnom](https://github.com/asciimoo/omnom), a webpage bookmarking and snapshotting service. Available as [services.omnom](options.html#opt-services.omnom.enable). +- [linkding](https://linkding.link/), self-hosted bookmark manager. Available as [services.linkding](options.html#opt-services.linkding). + - [Zenoh](https://zenoh.io/), a pub/sub/query protocol with low overhead. The Zenoh router daemon is available as [services.zenohd](options.html#opt-services.zenohd.enable) - [MaryTTS](https://github.com/marytts/marytts), an open-source, multilingual text-to-speech synthesis system written in pure Java. Available as [services.marytts](options.html#opt-services.marytts). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 06979a4df508f9..5733073007b254 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1499,6 +1499,7 @@ ./services/web-apps/lanraragi.nix ./services/web-apps/lemmy.nix ./services/web-apps/limesurvey.nix + ./services/web-apps/linkding.nix ./services/web-apps/mainsail.nix ./services/web-apps/mastodon.nix ./services/web-apps/matomo.nix diff --git a/nixos/modules/services/web-apps/linkding.nix b/nixos/modules/services/web-apps/linkding.nix new file mode 100644 index 00000000000000..9273e0e12cf882 --- /dev/null +++ b/nixos/modules/services/web-apps/linkding.nix @@ -0,0 +1,266 @@ +{ + config, + lib, + options, + pkgs, + ... +}: +{ + options.services.linkding = { + # General options + enable = lib.mkEnableOption "linkding service"; + + package = lib.mkPackageOption pkgs "linkding" { }; + + # Service configuration, in order of https://linkding.link/options/#list-of-options + enableBackgroundTasks = lib.mkOption { + default = true; + description = '' + Enable background tasks, such as creating snapshots for bookmarks on the the [Internet Archive Wayback Machine](https://archive.org/web/) + (`LD_DISABLE_BACKGROUND_TASKS` environment variable)[https://linkding.link/options/#ld_disable_background_tasks]. + ''; + type = lib.types.bool; + }; + + enableUrlValidation = lib.mkOption { + default = true; + description = '' + Enable URL validation for bookmarks + (`LD_DISABLE_URL_VALIDATION` environment variable)[https://linkding.link/options/#ld_disable_url_validation]. + ''; + type = lib.types.bool; + }; + + requestTimeoutSeconds = lib.mkOption { + default = 60; + description = '' + Request timeout in the uwsgi application server + (`LD_REQUEST_TIMEOUT` environment variable)[https://linkding.link/options/#ld_request_timeout]. + ''; + type = lib.types.ints.unsigned; + }; + + host = lib.mkOption { + default = "::1"; + description = '' + Address for socket to bind to + (`LD_SERVER_HOST` environment variable)[https://linkding.link/options/#ld_server_host]. + ''; + type = lib.types.str; + }; + + port = lib.mkOption { + default = 9090; + description = '' + Port number + (`LD_SERVER_PORT` environment variable)[https://linkding.link/options/#ld_server_port]. + ''; + type = lib.types.port; + }; + + contextPath = lib.mkOption { + default = null; + description = '' + Context path of the website + (`LD_CONTEXT_PATH` environment variable)[https://linkding.link/options/#ld_context_path]. + ''; + type = lib.types.nullOr lib.types.str; + }; + + enableAuthProxy = lib.mkOption { + default = false; + description = '' + Enable support for authentication proxy + (`LD_ENABLE_AUTH_PROXY` environment variable)[https://linkding.link/options/#ld_enable_auth_proxy]. + ''; + type = lib.types.bool; + }; + + enableOidc = lib.mkOption { + default = false; + description = '' + Enable support for OpenID Connect (OIDC) authentication + (`LD_ENABLE_OIDC` environment variable)[https://linkding.link/options/#ld_enable_oidc]. + ''; + type = lib.types.bool; + }; + + csrfTrustedOrigins = lib.mkOption { + default = [ ]; + description = '' + List of trusted origins to allow for POST requests + (`LD_CSRF_TRUSTED_ORIGINS` environment variable)[https://linkding.link/options/#ld_csrf_trusted_origins]. + ''; + type = lib.types.listOf lib.types.str; + }; + + logXForwardedFor = lib.mkOption { + default = false; + description = '' + Set uWSGI [log-x-forwarded-for](https://uwsgi-docs.readthedocs.io/en/latest/Options.html?#log-x-forwarded-for) parameter + (`LD_LOG_X_FORWARDED_FOR` environment variable)[https://linkding.link/options/#ld_log_x_forwarded_for]. + ''; + type = lib.types.bool; + }; + + databaseEngine = lib.mkOption { + default = "sqlite"; + description = '' + Database engine used by linkding to store data + (`LD_DB_ENGINE` environment variable)[https://linkding.link/options/#ld_db_engine]. + ''; + type = lib.types.enum [ + "postgres" + "sqlite" + ]; + }; + + databaseName = lib.mkOption { + default = "linkding"; + description = '' + Database name + (`LD_DB_DATABASE` environment variable)[https://linkding.link/options/#ld_db_database]. + ''; + type = lib.types.str; + }; + + databaseUser = lib.mkOption { + default = "linkding"; + description = '' + Database username + (`LD_DB_USER` environment variable)[https://linkding.link/options/#ld_db_user]. + ''; + type = lib.types.str; + }; + + databasePassword = lib.mkOption { + default = null; + description = '' + Database username + (`LD_DB_PASSWORD` environment variable)[https://linkding.link/options/#ld_db_password]. + ''; + type = lib.types.nullOr lib.types.str; + }; + + databaseHost = lib.mkOption { + default = "127.0.0.1"; + description = '' + Hostname or IP address of the database server + (`LD_DB_HOST` environment variable)[https://linkding.link/options/#ld_db_host]. + ''; + type = lib.types.str; + }; + + databasePort = lib.mkOption { + default = null; + description = '' + Database server port number + (`LD_DB_PORT` environment variable)[https://linkding.link/options/#ld_db_port]. + ''; + type = lib.types.nullOr lib.types.port; + }; + + databaseOptions = lib.mkOption { + default = { }; + description = '' + Database server port number + (`LD_DB_OPTIONS` environment variable)[https://linkding.link/options/#ld_db_options]. + ''; + type = lib.types.attrsOf lib.types.str; + }; + + faviconProviderUrl = lib.mkOption { + default = "https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url={url}&size=32"; + description = '' + URL used for downloading favicons + (`LD_FAVICON_PROVIDER` environment variable)[https://linkding.link/options/#ld_favicon_provider]. + ''; + type = lib.types.str; + }; + + singleFileTimeoutSeconds = lib.mkOption { + default = 60.0; + description = '' + How long to wait for the snapshot to complete + (`LD_REQUEST_TIMEOUT` environment variable)[https://linkding.link/options/#ld_request_timeout]. + ''; + type = lib.types.addCheck ( + lib.types.float + // { + name = "nonnegativeFloat"; + description = "nonnegative floating point number, meaning >=0"; + descriptionClass = "nonRestrictiveClause"; + } + ) (n: n >= 0); + }; + + singleFileOptions = lib.mkOption { + default = null; + description = '' + `single-file` options for creating HTML archive snapshots + (`LD_SINGLEFILE_OPTIONS` environment variable)[https://linkding.link/options/#ld_singlefile_options]. + ''; + type = lib.types.nullOr lib.types.str; + }; + + enableRequestLogs = lib.mkOption { + default = true; + description = '' + Set uWSGI [disable-logging](https://uwsgi-docs.readthedocs.io/en/latest/Options.html#disable-logging) parameter + (`LD_DISABLE_REQUEST_LOGS` environment variable)[https://linkding.link/options/#ld_disable_request_logs]. + ''; + type = lib.types.bool; + }; + }; + + config = lib.mkIf config.services.linkding.enable { + systemd.services.linkding = { + description = "linkding"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + environment = + let + boolToPythonRepr = value: if value then "True" else "False"; + in + { + LD_DISABLE_BACKGROUND_TASKS = boolToPythonRepr (!config.services.linkding.enableBackgroundTasks); + LD_DISABLE_URL_VALIDATION = boolToPythonRepr (!config.services.linkding.enableUrlValidation); + LD_REQUEST_TIMEOUT = builtins.toString config.services.linkding.requestTimeoutSeconds; + LD_SERVER_HOST = config.services.linkding.host; + LD_SERVER_PORT = builtins.toString config.services.linkding.port; + LD_ENABLE_AUTH_PROXY = boolToPythonRepr config.services.linkding.enableAuthProxy; + LD_ENABLE_OIDC = boolToPythonRepr config.services.linkding.enableOidc; + LD_LOG_X_FORWARDED_FOR = boolToPythonRepr config.services.linkding.logXForwardedFor; + LD_DB_ENGINE = config.services.linkding.databaseEngine; + LD_DB_DATABASE = config.services.linkding.databaseName; + LD_DB_USER = config.services.linkding.databaseUser; + LD_DB_HOST = config.services.linkding.databaseHost; + LD_DB_OPTIONS = builtins.toJSON config.services.linkding.databaseOptions; + LD_FAVICON_PROVIDER = config.services.linkding.faviconProviderUrl; + LD_SINGLEFILE_TIMEOUT_SEC = builtins.toString config.services.linkding.singleFileTimeoutSeconds; + LD_DISABLE_REQUEST_LOGS = boolToPythonRepr (!config.services.linkding.enableRequestLogs); + } + // lib.optionalAttrs (config.services.linkding.contextPath != null) { + LD_CONTEXT_PATH = config.services.linkding.contextPath; + } + // lib.optionalAttrs (config.services.linkding.csrfTrustedOrigins != [ ]) { + LD_CSRF_TRUSTED_ORIGINS = lib.strings.concatStringsSep "," config.services.linkding.csrfTrustedOrigins; + } + // lib.optionalAttrs (config.services.linkding.databasePassword != null) { + LD_DB_PASSWORD = config.services.linkding.databasePassword; + } + // lib.optionalAttrs (config.services.linkding.databasePort != null) { + LD_DB_PORT = config.services.linkding.databasePort; + } + // lib.optionalAttrs (config.services.linkding.singleFileOptions != null) { + LD_SINGLEFILE_OPTIONS = config.services.linkding.singleFileOptions; + }; + + serviceConfig.ExecStart = "${config.services.linkding.package}/bootstrap.sh"; + }; + }; + + meta.maintainers = [ lib.maintainers.l0b0 ]; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 613ff1314488a6..25d8de90869764 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -442,6 +442,7 @@ in { installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {}); invidious = handleTest ./invidious.nix {}; isolate = handleTest ./isolate.nix {}; + linkding = discoverTests (import ./web-apps/linkding { inherit handleTest; }); livebook-service = handleTest ./livebook-service.nix {}; pyload = handleTest ./pyload.nix {}; oci-containers = handleTestOn ["aarch64-linux" "x86_64-linux"] ./oci-containers.nix {}; diff --git a/nixos/tests/web-apps/linkding/default.nix b/nixos/tests/web-apps/linkding/default.nix new file mode 100644 index 00000000000000..696fd6111d7085 --- /dev/null +++ b/nixos/tests/web-apps/linkding/default.nix @@ -0,0 +1,5 @@ +{ handleTest }: +{ + defaults = handleTest ./defaults.nix { }; + overrides = handleTest ./overrides.nix { }; +} diff --git a/nixos/tests/web-apps/linkding/defaults.nix b/nixos/tests/web-apps/linkding/defaults.nix new file mode 100644 index 00000000000000..1f8b583eb9f154 --- /dev/null +++ b/nixos/tests/web-apps/linkding/defaults.nix @@ -0,0 +1,20 @@ +import ../../make-test-python.nix ( + { lib, ... }: + let + nodeName = "server"; + in + { + name = "linkding-defaults"; + + nodes."${nodeName}" = { + services.linkding.enable = true; + }; + + testScript = '' + ${nodeName}.wait_for_unit("linkding.service") + ${nodeName}.succeed("curl --fail http://127.0.0.1:9090") + ''; + + meta.maintainers = [ lib.maintainers.l0b0 ]; + } +) diff --git a/nixos/tests/web-apps/linkding/overrides.nix b/nixos/tests/web-apps/linkding/overrides.nix new file mode 100644 index 00000000000000..dca53b3067455b --- /dev/null +++ b/nixos/tests/web-apps/linkding/overrides.nix @@ -0,0 +1,33 @@ +import ../../make-test-python.nix ( + { lib, ... }: + let + nodeName = "server"; + in + { + name = "linkding-overrides"; + + nodes."${nodeName}" = + { + options, + pkgs, + ... + }: + { + services.linkding = { + enable = true; + package = pkgs.linkding.overrideAttrs (_old: { + name = "custom-linkding"; + }); + host = "127.0.0.2"; + port = 8000; + }; + }; + + testScript = '' + ${nodeName}.wait_for_unit("linkding.service") + ${nodeName}.succeed("curl --fail http://127.0.0.2:8000") + ''; + + meta.maintainers = [ lib.maintainers.l0b0 ]; + } +)