1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.karakeep;
9
10 karakeepEnv = lib.mkMerge [
11 { DATA_DIR = "/var/lib/karakeep"; }
12 (lib.mkIf cfg.meilisearch.enable {
13 MEILI_ADDR = "http://127.0.0.1:${toString config.services.meilisearch.listenPort}";
14 })
15 (lib.mkIf cfg.browser.enable {
16 BROWSER_WEB_URL = "http://127.0.0.1:${toString cfg.browser.port}";
17 })
18 cfg.extraEnvironment
19 ];
20
21 environmentFiles = [
22 "/var/lib/karakeep/settings.env"
23 ] ++ (lib.optional (cfg.environmentFile != null) cfg.environmentFile);
24in
25{
26 options = {
27 services.karakeep = {
28 enable = lib.mkEnableOption "Enable the Karakeep service";
29 package = lib.mkPackageOption pkgs "karakeep" { };
30
31 extraEnvironment = lib.mkOption {
32 description = ''
33 Environment variables to pass to Karakaeep. This is how most settings
34 can be configured. Changing DATA_DIR is possible but not supported.
35
36 See https://docs.karakeep.app/configuration/
37 '';
38 type = lib.types.attrsOf lib.types.str;
39 default = { };
40 example = lib.literalExpression ''
41 {
42 PORT = "1234";
43 DISABLE_SIGNUPS = "true";
44 DISABLE_NEW_RELEASE_CHECK = "true";
45 }
46 '';
47 };
48
49 environmentFile = lib.mkOption {
50 type = lib.types.nullOr lib.types.path;
51 default = null;
52 description = ''
53 An optional path to an environment file that will be used in the web and workers
54 services. This is useful for loading private keys.
55 '';
56 example = "/var/lib/karakeep/secrets.env";
57 };
58
59 browser = {
60 enable = lib.mkOption {
61 description = ''
62 Enable the karakeep-browser service that runs a chromium instance in
63 the background with debugging ports exposed. This is necessary for
64 certain features like screenshots.
65 '';
66 type = lib.types.bool;
67 default = true;
68 };
69 port = lib.mkOption {
70 description = "The port the browser should run on.";
71 type = lib.types.port;
72 default = 9222;
73 };
74 exe = lib.mkOption {
75 description = "The browser executable (must be Chrome-like).";
76 type = lib.types.str;
77 default = "${pkgs.chromium}/bin/chromium";
78 defaultText = lib.literalExpression "\${pkgs.chromium}/bin/chromium";
79 example = lib.literalExpression "\${pkgs.google-chrome}/bin/google-chrome-stable";
80 };
81 };
82
83 meilisearch = {
84 enable = lib.mkOption {
85 type = lib.types.bool;
86 default = true;
87 description = ''
88 Enable Meilisearch and configure Karakeep to use it. Meilisearch is
89 required for text search.
90 '';
91 };
92 };
93 };
94 };
95
96 config = lib.mkIf cfg.enable {
97 environment.systemPackages = [ cfg.package ];
98
99 users.groups.karakeep = { };
100 users.users.karakeep = {
101 isSystemUser = true;
102 group = "karakeep";
103 };
104
105 services.meilisearch = lib.mkIf cfg.meilisearch.enable {
106 enable = true;
107 };
108
109 systemd.services.karakeep-init = {
110 description = "Initialize Karakeep Data";
111 wantedBy = [ "multi-user.target" ];
112 after = [ "network.target" ];
113 partOf = [ "karakeep.service" ];
114 path = [ pkgs.openssl ];
115 script = ''
116 umask 0077
117
118 if [ ! -f "$STATE_DIRECTORY/settings.env" ]; then
119 cat <<EOF >"$STATE_DIRECTORY/settings.env"
120 # Generated by NixOS Karakeep module
121 MEILI_MASTER_KEY=$(openssl rand -base64 36)
122 NEXTAUTH_SECRET=$(openssl rand -base64 36)
123 EOF
124 fi
125
126 export DATA_DIR="$STATE_DIRECTORY"
127 exec "${cfg.package}/lib/karakeep/migrate"
128 '';
129 serviceConfig = {
130 Type = "oneshot";
131 RemainAfterExit = true;
132 User = "karakeep";
133 Group = "karakeep";
134 StateDirectory = "karakeep";
135 PrivateTmp = "yes";
136 };
137 };
138
139 systemd.services.karakeep-workers = {
140 description = "Karakeep Workers";
141 wantedBy = [ "multi-user.target" ];
142 after = [
143 "network.target"
144 "karakeep-init.service"
145 ];
146 partOf = [ "karakeep.service" ];
147 path = [
148 pkgs.monolith
149 pkgs.yt-dlp
150 ];
151 environment = karakeepEnv;
152 serviceConfig = {
153 User = "karakeep";
154 Group = "karakeep";
155 ExecStart = "${cfg.package}/lib/karakeep/start-workers";
156 StateDirectory = "karakeep";
157 EnvironmentFile = environmentFiles;
158 PrivateTmp = "yes";
159 };
160 };
161
162 systemd.services.karakeep-web = {
163 description = "Karakeep Web";
164 wantedBy = [ "multi-user.target" ];
165 after = [
166 "network.target"
167 "karakeep-init.service"
168 "karakeep-workers.service"
169 ];
170 partOf = [ "karakeep.service" ];
171 environment = karakeepEnv;
172 serviceConfig = {
173 ExecStart = "${cfg.package}/lib/karakeep/start-web";
174 User = "karakeep";
175 Group = "karakeep";
176 StateDirectory = "karakeep";
177 EnvironmentFile = environmentFiles;
178 PrivateTmp = "yes";
179 };
180 };
181
182 systemd.services.karakeep-browser = lib.mkIf cfg.browser.enable {
183 wantedBy = [ "multi-user.target" ];
184 after = [ "network.target" ];
185 partOf = [ "karakeep.service" ];
186 script = ''
187 export HOME="$CACHE_DIRECTORY"
188 exec ${cfg.browser.exe} \
189 --headless --no-sandbox --disable-gpu --disable-dev-shm-usage \
190 --remote-debugging-address=127.0.0.1 \
191 --remote-debugging-port=${toString cfg.browser.port} \
192 --hide-scrollbars \
193 --user-data-dir="$STATE_DIRECTORY"
194 '';
195 serviceConfig = {
196 Type = "simple";
197 Restart = "on-failure";
198
199 CacheDirectory = "karakeep-browser";
200 StateDirectory = "karakeep-browser";
201
202 DevicePolicy = "closed";
203 DynamicUser = true;
204 LockPersonality = true;
205 NoNewPrivileges = true;
206 PrivateDevices = true;
207 PrivateTmp = true;
208 PrivateUsers = true;
209 ProtectClock = true;
210 ProtectControlGroups = true;
211 ProtectHostname = true;
212 ProtectKernelLogs = true;
213 ProtectKernelModules = true;
214 ProtectKernelTunables = true;
215 ProtectSystem = "strict";
216 RestrictNamespaces = true;
217 RestrictRealtime = true;
218 };
219 };
220 };
221
222 meta = {
223 maintainers = [ lib.maintainers.three ];
224 };
225}