{ config, lib, pkgs, ... }: let cfg = config.services.karakeep; karakeepEnv = lib.mkMerge [ { DATA_DIR = "/var/lib/karakeep"; } (lib.mkIf cfg.meilisearch.enable { MEILI_ADDR = "http://127.0.0.1:${toString config.services.meilisearch.listenPort}"; }) (lib.mkIf cfg.browser.enable { BROWSER_WEB_URL = "http://127.0.0.1:${toString cfg.browser.port}"; }) cfg.extraEnvironment ]; environmentFiles = [ "/var/lib/karakeep/settings.env" ] ++ (lib.optional (cfg.environmentFile != null) cfg.environmentFile); in { options = { services.karakeep = { enable = lib.mkEnableOption "Enable the Karakeep service"; package = lib.mkPackageOption pkgs "karakeep" { }; extraEnvironment = lib.mkOption { description = '' Environment variables to pass to Karakaeep. This is how most settings can be configured. Changing DATA_DIR is possible but not supported. See ''; type = lib.types.attrsOf lib.types.str; default = { }; example = lib.literalExpression '' { PORT = "1234"; DISABLE_SIGNUPS = "true"; DISABLE_NEW_RELEASE_CHECK = "true"; } ''; }; environmentFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = '' An optional path to an environment file that will be used in the web and workers services. This is useful for loading private keys. ''; example = "/var/lib/karakeep/secrets.env"; }; browser = { enable = lib.mkOption { description = '' Enable the karakeep-browser service that runs a chromium instance in the background with debugging ports exposed. This is necessary for certain features like screenshots. ''; type = lib.types.bool; default = true; }; port = lib.mkOption { description = "The port the browser should run on."; type = lib.types.port; default = 9222; }; exe = lib.mkOption { description = "The browser executable (must be Chrome-like)."; type = lib.types.str; default = "${pkgs.chromium}/bin/chromium"; defaultText = lib.literalExpression "\${pkgs.chromium}/bin/chromium"; example = lib.literalExpression "\${pkgs.google-chrome}/bin/google-chrome-stable"; }; }; meilisearch = { enable = lib.mkOption { type = lib.types.bool; default = true; description = '' Enable Meilisearch and configure Karakeep to use it. Meilisearch is required for text search. ''; }; }; }; }; config = lib.mkIf cfg.enable { environment.systemPackages = [ cfg.package ]; users.groups.karakeep = { }; users.users.karakeep = { isSystemUser = true; group = "karakeep"; }; services.meilisearch = lib.mkIf cfg.meilisearch.enable { enable = true; }; systemd.services.karakeep-init = { description = "Initialize Karakeep Data"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; partOf = [ "karakeep.service" ]; path = [ pkgs.openssl ]; script = '' umask 0077 if [ ! -f "$STATE_DIRECTORY/settings.env" ]; then cat <"$STATE_DIRECTORY/settings.env" # Generated by NixOS Karakeep module MEILI_MASTER_KEY=$(openssl rand -base64 36) NEXTAUTH_SECRET=$(openssl rand -base64 36) EOF fi export DATA_DIR="$STATE_DIRECTORY" exec "${cfg.package}/lib/karakeep/migrate" ''; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; User = "karakeep"; Group = "karakeep"; StateDirectory = "karakeep"; PrivateTmp = "yes"; }; }; systemd.services.karakeep-workers = { description = "Karakeep Workers"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" "karakeep-init.service" ]; partOf = [ "karakeep.service" ]; path = [ pkgs.monolith pkgs.yt-dlp ]; environment = karakeepEnv; serviceConfig = { User = "karakeep"; Group = "karakeep"; ExecStart = "${cfg.package}/lib/karakeep/start-workers"; StateDirectory = "karakeep"; EnvironmentFile = environmentFiles; PrivateTmp = "yes"; }; }; systemd.services.karakeep-web = { description = "Karakeep Web"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" "karakeep-init.service" "karakeep-workers.service" ]; partOf = [ "karakeep.service" ]; environment = karakeepEnv; serviceConfig = { ExecStart = "${cfg.package}/lib/karakeep/start-web"; User = "karakeep"; Group = "karakeep"; StateDirectory = "karakeep"; EnvironmentFile = environmentFiles; PrivateTmp = "yes"; }; }; systemd.services.karakeep-browser = lib.mkIf cfg.browser.enable { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; partOf = [ "karakeep.service" ]; script = '' export HOME="$CACHE_DIRECTORY" exec ${cfg.browser.exe} \ --headless --no-sandbox --disable-gpu --disable-dev-shm-usage \ --remote-debugging-address=127.0.0.1 \ --remote-debugging-port=${toString cfg.browser.port} \ --hide-scrollbars \ --user-data-dir="$STATE_DIRECTORY" ''; serviceConfig = { Type = "simple"; Restart = "on-failure"; CacheDirectory = "karakeep-browser"; StateDirectory = "karakeep-browser"; DevicePolicy = "closed"; DynamicUser = true; LockPersonality = true; NoNewPrivileges = true; PrivateDevices = true; PrivateTmp = true; PrivateUsers = true; ProtectClock = true; ProtectControlGroups = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProtectSystem = "strict"; RestrictNamespaces = true; RestrictRealtime = true; }; }; }; meta = { maintainers = [ lib.maintainers.three ]; }; }