|
| 1 | +{ |
| 2 | + config, |
| 3 | + pkgs, |
| 4 | + lib, |
| 5 | + ... |
| 6 | +}: |
| 7 | + |
| 8 | +let |
| 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; |
| 22 | +in |
| 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 | +} |
0 commit comments