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