1{
2 lib,
3 pkgs,
4 config,
5 ...
6}:
7
8let
9 inherit (lib)
10 concatMap
11 concatStringsSep
12 getExe
13 maintainers
14 mkEnableOption
15 mkIf
16 mkOption
17 mkPackageOption
18 optional
19 optionalAttrs
20 ;
21 inherit (lib.types)
22 bool
23 path
24 str
25 submodule
26 ;
27
28 cfg = config.services.pocket-id;
29
30 format = pkgs.formats.keyValue { };
31 settingsFile = format.generate "pocket-id-env-vars" cfg.settings;
32in
33{
34 meta.maintainers = with maintainers; [
35 gepbird
36 ymstnt
37 ];
38
39 options.services.pocket-id = {
40 enable = mkEnableOption "Pocket ID server";
41
42 package = mkPackageOption pkgs "pocket-id" { };
43
44 environmentFile = mkOption {
45 type = path;
46 description = ''
47 Path to an environment file loaded for the Pocket ID service.
48
49 This can be used to securely store tokens and secrets outside of the world-readable Nix store.
50
51 Example contents of the file:
52 MAXMIND_LICENSE_KEY=your-license-key
53 '';
54 default = "/dev/null";
55 example = "/var/lib/secrets/pocket-id";
56 };
57
58 settings = mkOption {
59 type = submodule {
60 freeformType = format.type;
61
62 options = {
63 APP_URL = mkOption {
64 type = str;
65 description = ''
66 The URL where you will access the app.
67 '';
68 default = "http://localhost";
69 };
70
71 TRUST_PROXY = mkOption {
72 type = bool;
73 description = ''
74 Whether the app is behind a reverse proxy.
75 '';
76 default = false;
77 };
78
79 ANALYTICS_DISABLED = mkOption {
80 type = bool;
81 description = ''
82 Whether to disable analytics.
83
84 See [docs page](https://pocket-id.org/docs/configuration/analytics/).
85 '';
86 default = false;
87 };
88 };
89 };
90
91 default = { };
92
93 description = ''
94 Environment variables that will be passed to Pocket ID, see
95 [configuration options](https://pocket-id.org/docs/configuration/environment-variables)
96 for supported values.
97 '';
98 };
99
100 dataDir = mkOption {
101 type = path;
102 default = "/var/lib/pocket-id";
103 description = ''
104 The directory where Pocket ID will store its data, such as the database.
105 '';
106 };
107
108 user = mkOption {
109 type = str;
110 default = "pocket-id";
111 description = "User account under which Pocket ID runs.";
112 };
113
114 group = mkOption {
115 type = str;
116 default = "pocket-id";
117 description = "Group account under which Pocket ID runs.";
118 };
119 };
120
121 config = mkIf cfg.enable {
122 warnings =
123 optional (cfg.settings ? MAXMIND_LICENSE_KEY)
124 "config.services.pocket-id.settings.MAXMIND_LICENSE_KEY will be stored as plaintext in the Nix store. Use config.services.pocket-id.environmentFile instead."
125 ++
126 concatMap
127 (
128 # Added 2025-05-27
129 setting:
130 optional (cfg.settings ? "${setting}") ''
131 config.services.pocket-id.settings.${setting} is deprecated.
132 See https://pocket-id.org/docs/setup/migrate-to-v1/ for migration instructions.
133 ''
134 )
135 [
136 "PUBLIC_APP_URL"
137 "PUBLIC_UI_CONFIG_DISABLED"
138 "CADDY_DISABLED"
139 "CADDY_PORT"
140 "BACKEND_PORT"
141 "POSTGRES_CONNECTION_STRING"
142 "SQLITE_DB_PATH"
143 "INTERNAL_BACKEND_URL"
144 ];
145
146 systemd.tmpfiles.rules = [
147 "d ${cfg.dataDir} 0755 ${cfg.user} ${cfg.group}"
148 ];
149
150 systemd.services = {
151 pocket-id = {
152 description = "Pocket ID";
153 after = [ "network.target" ];
154 wantedBy = [ "multi-user.target" ];
155 restartTriggers = [
156 cfg.package
157 cfg.environmentFile
158 settingsFile
159 ];
160
161 serviceConfig = {
162 Type = "simple";
163 User = cfg.user;
164 Group = cfg.group;
165 WorkingDirectory = cfg.dataDir;
166 ExecStart = getExe cfg.package;
167 Restart = "always";
168 EnvironmentFile = [
169 cfg.environmentFile
170 settingsFile
171 ];
172
173 # Hardening
174 AmbientCapabilities = "";
175 CapabilityBoundingSet = "";
176 DeviceAllow = "";
177 DevicePolicy = "closed";
178 #IPAddressDeny = "any"; # provides the service through network
179 LockPersonality = true;
180 MemoryDenyWriteExecute = true;
181 NoNewPrivileges = true;
182 PrivateDevices = true;
183 PrivateNetwork = false; # provides the service through network
184 PrivateTmp = true;
185 PrivateUsers = true;
186 ProcSubset = "pid";
187 ProtectClock = true;
188 ProtectControlGroups = true;
189 ProtectHome = true;
190 ProtectHostname = true;
191 ProtectKernelLogs = true;
192 ProtectKernelModules = true;
193 ProtectKernelTunables = true;
194 ProtectProc = "invisible";
195 ProtectSystem = "strict";
196 ReadWritePaths = [ cfg.dataDir ];
197 RemoveIPC = true;
198 RestrictAddressFamilies = [
199 "AF_UNIX"
200 "AF_INET"
201 "AF_INET6"
202 ];
203 RestrictNamespaces = true;
204 RestrictRealtime = true;
205 RestrictSUIDSGID = true;
206 SystemCallArchitectures = "native";
207 SystemCallFilter = concatStringsSep " " [
208 "~"
209 "@clock"
210 "@cpu-emulation"
211 "@debug"
212 "@module"
213 "@mount"
214 "@obsolete"
215 "@privileged"
216 "@raw-io"
217 "@reboot"
218 "@resources"
219 "@swap"
220 ];
221 UMask = "0077";
222 };
223 };
224 };
225
226 users.users = optionalAttrs (cfg.user == "pocket-id") {
227 pocket-id = {
228 isSystemUser = true;
229 group = cfg.group;
230 description = "Pocket ID backend user";
231 home = cfg.dataDir;
232 };
233 };
234
235 users.groups = optionalAttrs (cfg.group == "pocket-id") {
236 pocket-id = { };
237 };
238 };
239}