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