at 25.11-pre 9.3 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: 7 8let 9 cfg = config.services.glitchtip; 10 pkg = cfg.package; 11 inherit (pkg.passthru) python; 12 13 environment = lib.mapAttrs ( 14 _: value: 15 if value == true then 16 "True" 17 else if value == false then 18 "False" 19 else 20 toString value 21 ) cfg.settings; 22in 23 24{ 25 meta.maintainers = with lib.maintainers; [ 26 defelo 27 felbinger 28 ]; 29 30 options = { 31 services.glitchtip = { 32 enable = lib.mkEnableOption "GlitchTip"; 33 34 package = lib.mkPackageOption pkgs "glitchtip" { }; 35 36 user = lib.mkOption { 37 type = lib.types.str; 38 description = "The user account under which GlitchTip runs."; 39 default = "glitchtip"; 40 }; 41 42 group = lib.mkOption { 43 type = lib.types.str; 44 description = "The group under which GlitchTip runs."; 45 default = "glitchtip"; 46 }; 47 48 listenAddress = lib.mkOption { 49 type = lib.types.str; 50 description = "The address to listen on."; 51 default = "127.0.0.1"; 52 example = "0.0.0.0"; 53 }; 54 55 port = lib.mkOption { 56 type = lib.types.port; 57 description = "The port to listen on."; 58 default = 8000; 59 }; 60 61 settings = lib.mkOption { 62 description = '' 63 Configuration of GlitchTip. See <https://glitchtip.com/documentation/install#configuration> for more information. 64 ''; 65 default = { }; 66 defaultText = lib.literalExpression '' 67 { 68 DEBUG = 0; 69 DEBUG_TOOLBAR = 0; 70 DATABASE_URL = lib.mkIf config.services.glitchtip.database.createLocally "postgresql://@/glitchtip"; 71 REDIS_URL = lib.mkIf config.services.glitchtip.redis.createLocally "unix://''${config.services.redis.servers.glitchtip.unixSocket}"; 72 CELERY_BROKER_URL = lib.mkIf config.services.glitchtip.redis.createLocally "redis+socket://''${config.services.redis.servers.glitchtip.unixSocket}"; 73 } 74 ''; 75 example = { 76 GLITCHTIP_DOMAIN = "https://glitchtip.example.com"; 77 DATABASE_URL = "postgres://postgres:postgres@postgres/postgres"; 78 }; 79 80 type = lib.types.submodule { 81 freeformType = 82 with lib.types; 83 attrsOf (oneOf [ 84 str 85 int 86 bool 87 ]); 88 89 options = { 90 GLITCHTIP_DOMAIN = lib.mkOption { 91 type = lib.types.str; 92 description = "The URL under which GlitchTip is externally reachable."; 93 example = "https://glitchtip.example.com"; 94 }; 95 96 ENABLE_USER_REGISTRATION = lib.mkOption { 97 type = lib.types.bool; 98 description = '' 99 When true, any user will be able to register. When false, user self-signup is disabled after the first user is registered. Subsequent users must be created by a superuser on the backend and organization invitations may only be sent to existing users. 100 ''; 101 default = false; 102 }; 103 104 ENABLE_ORGANIZATION_CREATION = lib.mkOption { 105 type = lib.types.bool; 106 description = '' 107 When false, only superusers will be able to create new organizations after the first. When true, any user can create a new organization. 108 ''; 109 default = false; 110 }; 111 }; 112 }; 113 }; 114 115 environmentFiles = lib.mkOption { 116 type = lib.types.listOf lib.types.path; 117 default = [ ]; 118 example = [ "/run/secrets/glitchtip.env" ]; 119 description = '' 120 Files to load environment variables from in addition to [](#opt-services.glitchtip.settings). 121 This is useful to avoid putting secrets into the nix store. 122 See <https://glitchtip.com/documentation/install#configuration> for more information. 123 ''; 124 }; 125 126 database.createLocally = lib.mkOption { 127 type = lib.types.bool; 128 default = true; 129 description = '' 130 Whether to enable and configure a local PostgreSQL database server. 131 ''; 132 }; 133 134 redis.createLocally = lib.mkOption { 135 type = lib.types.bool; 136 default = true; 137 description = '' 138 Whether to enable and configure a local Redis instance. 139 ''; 140 }; 141 142 gunicorn.extraArgs = lib.mkOption { 143 type = lib.types.listOf lib.types.str; 144 default = [ ]; 145 description = "Extra arguments for gunicorn."; 146 }; 147 148 celery.extraArgs = lib.mkOption { 149 type = lib.types.listOf lib.types.str; 150 default = [ ]; 151 description = "Extra arguments for celery."; 152 }; 153 }; 154 }; 155 156 config = lib.mkIf cfg.enable { 157 services.glitchtip.settings = { 158 DEBUG = lib.mkDefault 0; 159 DEBUG_TOOLBAR = lib.mkDefault 0; 160 PYTHONPATH = "${python.pkgs.makePythonPath pkg.propagatedBuildInputs}:${pkg}/lib/glitchtip"; 161 DATABASE_URL = lib.mkIf cfg.database.createLocally "postgresql://@/glitchtip"; 162 REDIS_URL = lib.mkIf cfg.redis.createLocally "unix://${config.services.redis.servers.glitchtip.unixSocket}"; 163 CELERY_BROKER_URL = lib.mkIf cfg.redis.createLocally "redis+socket://${config.services.redis.servers.glitchtip.unixSocket}"; 164 GLITCHTIP_VERSION = pkg.version; 165 }; 166 167 systemd.services = 168 let 169 commonService = { 170 wantedBy = [ "multi-user.target" ]; 171 172 wants = [ "network-online.target" ]; 173 requires = 174 lib.optional cfg.database.createLocally "postgresql.service" 175 ++ lib.optional cfg.redis.createLocally "redis-glitchtip.service"; 176 after = 177 [ "network-online.target" ] 178 ++ lib.optional cfg.database.createLocally "postgresql.service" 179 ++ lib.optional cfg.redis.createLocally "redis-glitchtip.service"; 180 181 inherit environment; 182 }; 183 184 commonServiceConfig = { 185 User = cfg.user; 186 Group = cfg.group; 187 RuntimeDirectory = "glitchtip"; 188 StateDirectory = "glitchtip"; 189 EnvironmentFile = cfg.environmentFiles; 190 WorkingDirectory = "${pkg}/lib/glitchtip"; 191 192 # hardening 193 AmbientCapabilities = ""; 194 CapabilityBoundingSet = [ "" ]; 195 DevicePolicy = "closed"; 196 LockPersonality = true; 197 MemoryDenyWriteExecute = true; 198 NoNewPrivileges = true; 199 PrivateDevices = true; 200 PrivateTmp = true; 201 PrivateUsers = true; 202 ProcSubset = "pid"; 203 ProtectClock = true; 204 ProtectControlGroups = true; 205 ProtectHome = true; 206 ProtectHostname = true; 207 ProtectKernelLogs = true; 208 ProtectKernelModules = true; 209 ProtectKernelTunables = true; 210 ProtectProc = "invisible"; 211 ProtectSystem = "strict"; 212 RemoveIPC = true; 213 RestrictAddressFamilies = [ "AF_INET AF_INET6 AF_UNIX" ]; 214 RestrictNamespaces = true; 215 RestrictRealtime = true; 216 RestrictSUIDSGID = true; 217 SystemCallArchitectures = "native"; 218 SystemCallFilter = [ 219 "@system-service" 220 "~@privileged" 221 "~@resources" 222 ]; 223 UMask = "0077"; 224 }; 225 in 226 { 227 glitchtip = commonService // { 228 description = "GlitchTip"; 229 230 preStart = '' 231 ${lib.getExe pkg} migrate 232 ''; 233 234 serviceConfig = commonServiceConfig // { 235 ExecStart = '' 236 ${lib.getExe python.pkgs.gunicorn} \ 237 --bind=${cfg.listenAddress}:${toString cfg.port} \ 238 ${lib.concatStringsSep " " cfg.gunicorn.extraArgs} \ 239 glitchtip.wsgi 240 ''; 241 }; 242 }; 243 244 glitchtip-worker = commonService // { 245 description = "GlitchTip Job Runner"; 246 247 serviceConfig = commonServiceConfig // { 248 ExecStart = '' 249 ${lib.getExe python.pkgs.celery} \ 250 -A glitchtip worker \ 251 -B -s /run/glitchtip/celerybeat-schedule \ 252 ${lib.concatStringsSep " " cfg.celery.extraArgs} 253 ''; 254 }; 255 }; 256 }; 257 258 services.postgresql = lib.mkIf cfg.database.createLocally { 259 enable = true; 260 ensureDatabases = [ "glitchtip" ]; 261 ensureUsers = [ 262 { 263 name = "glitchtip"; 264 ensureDBOwnership = true; 265 } 266 ]; 267 }; 268 269 services.redis.servers.glitchtip.enable = cfg.redis.createLocally; 270 271 users.users = lib.mkIf (cfg.user == "glitchtip") { 272 glitchtip = { 273 home = "/var/lib/glitchtip"; 274 group = cfg.group; 275 extraGroups = lib.optionals cfg.redis.createLocally [ "redis-glitchtip" ]; 276 isSystemUser = true; 277 }; 278 }; 279 280 users.groups = lib.mkIf (cfg.group == "glitchtip") { glitchtip = { }; }; 281 282 environment.systemPackages = 283 let 284 glitchtip-manage = pkgs.writeShellScriptBin "glitchtip-manage" '' 285 set -o allexport 286 ${lib.toShellVars environment} 287 ${lib.concatMapStringsSep "\n" (f: "source ${f}") cfg.environmentFiles} 288 ${config.security.wrapperDir}/sudo -E -u ${cfg.user} ${lib.getExe pkg} "$@" 289 ''; 290 in 291 [ glitchtip-manage ]; 292 }; 293}