diff --git a/modules/system/default.nix b/modules/system/default.nix index 4d9961b..9b17a5e 100644 --- a/modules/system/default.nix +++ b/modules/system/default.nix @@ -8,7 +8,7 @@ ./collabora.nix ./cloudreve.nix ./do-agent.nix - ./glitchtip.nix + ./glitchtip ./element-web.nix ./fieldseeker-sync.nix ./fish.nix diff --git a/modules/system/glitchtip.nix b/modules/system/glitchtip/default.nix similarity index 83% rename from modules/system/glitchtip.nix rename to modules/system/glitchtip/default.nix index 3449ba3..0d083b4 100644 --- a/modules/system/glitchtip.nix +++ b/modules/system/glitchtip/default.nix @@ -1,6 +1,10 @@ { pkgs, lib, config, ... }: with lib; { + disabledModules = [ "services/web-apps/glitchtip.nix" ]; + imports = [ + ./glitchtip.nix + ]; options.myModules.glitchtip.enable = mkEnableOption "custom glitchtip configuration"; config = mkIf config.myModules.glitchtip.enable { @@ -21,7 +25,7 @@ with lib; mode = "0440"; owner = "glitchtip"; restartUnits = ["glitchtip.service"]; - sopsFile = ../../secrets/glitchtip.env; + sopsFile = ../../../secrets/glitchtip.env; }; }; } diff --git a/modules/system/glitchtip/glitchtip.nix b/modules/system/glitchtip/glitchtip.nix new file mode 100644 index 0000000..9cc32ff --- /dev/null +++ b/modules/system/glitchtip/glitchtip.nix @@ -0,0 +1,295 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + cfg = config.services.glitchtip; + pkg = cfg.package; + inherit (pkg.passthru) python; + + environment = lib.mapAttrs ( + _: value: + if value == true then + "True" + else if value == false then + "False" + else + toString value + ) cfg.settings; +in + +{ + meta.maintainers = with lib.maintainers; [ + defelo + felbinger + ]; + + options = { + services.glitchtip = { + enable = lib.mkEnableOption "GlitchTip"; + + package = lib.mkPackageOption pkgs "glitchtip" { }; + + user = lib.mkOption { + type = lib.types.str; + description = "The user account under which GlitchTip runs."; + default = "glitchtip"; + }; + + group = lib.mkOption { + type = lib.types.str; + description = "The group under which GlitchTip runs."; + default = "glitchtip"; + }; + + listenAddress = lib.mkOption { + type = lib.types.str; + description = "The address to listen on."; + default = "127.0.0.1"; + example = "0.0.0.0"; + }; + + port = lib.mkOption { + type = lib.types.port; + description = "The port to listen on."; + default = 8000; + }; + + settings = lib.mkOption { + description = '' + Configuration of GlitchTip. See for more information. + ''; + default = { }; + defaultText = lib.literalExpression '' + { + DEBUG = 0; + DEBUG_TOOLBAR = 0; + DATABASE_URL = lib.mkIf config.services.glitchtip.database.createLocally "postgresql://@/glitchtip"; + REDIS_URL = lib.mkIf config.services.glitchtip.redis.createLocally "unix://''${config.services.redis.servers.glitchtip.unixSocket}"; + CELERY_BROKER_URL = lib.mkIf config.services.glitchtip.redis.createLocally "redis+socket://''${config.services.redis.servers.glitchtip.unixSocket}"; + } + ''; + example = { + GLITCHTIP_DOMAIN = "https://glitchtip.example.com"; + DATABASE_URL = "postgres://postgres:postgres@postgres/postgres"; + }; + + type = lib.types.submodule { + freeformType = + with lib.types; + attrsOf (oneOf [ + str + int + bool + ]); + + options = { + GLITCHTIP_DOMAIN = lib.mkOption { + type = lib.types.str; + description = "The URL under which GlitchTip is externally reachable."; + example = "https://glitchtip.example.com"; + }; + + ENABLE_USER_REGISTRATION = lib.mkOption { + type = lib.types.bool; + description = '' + 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. + ''; + default = false; + }; + + ENABLE_ORGANIZATION_CREATION = lib.mkOption { + type = lib.types.bool; + description = '' + When false, only superusers will be able to create new organizations after the first. When true, any user can create a new organization. + ''; + default = false; + }; + }; + }; + }; + + environmentFiles = lib.mkOption { + type = lib.types.listOf lib.types.path; + default = [ ]; + example = [ "/run/secrets/glitchtip.env" ]; + description = '' + Files to load environment variables from in addition to [](#opt-services.glitchtip.settings). + This is useful to avoid putting secrets into the nix store. + See for more information. + ''; + }; + + database.createLocally = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether to enable and configure a local PostgreSQL database server. + ''; + }; + + redis.createLocally = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether to enable and configure a local Redis instance. + ''; + }; + + gunicorn.extraArgs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Extra arguments for gunicorn."; + }; + + celery.extraArgs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Extra arguments for celery."; + }; + }; + }; + + config = lib.mkIf cfg.enable { + services.glitchtip.settings = { + DEBUG = lib.mkDefault 0; + DEBUG_TOOLBAR = lib.mkDefault 0; + PYTHONPATH = "${python.pkgs.makePythonPath pkg.propagatedBuildInputs}:${pkg}/lib/glitchtip"; + DATABASE_URL = lib.mkIf cfg.database.createLocally "postgresql://@/glitchtip"; + REDIS_URL = lib.mkIf cfg.redis.createLocally "unix://${config.services.redis.servers.glitchtip.unixSocket}"; + CELERY_BROKER_URL = lib.mkIf cfg.redis.createLocally "redis+socket://${config.services.redis.servers.glitchtip.unixSocket}"; + GLITCHTIP_VERSION = pkg.version; + }; + + systemd.services = + let + commonService = { + wantedBy = [ "multi-user.target" ]; + + wants = [ "network-online.target" ]; + requires = + lib.optional cfg.database.createLocally "postgresql.service" + ++ lib.optional cfg.redis.createLocally "redis-glitchtip.service"; + after = [ + "network-online.target" + ] + ++ lib.optional cfg.database.createLocally "postgresql.service" + ++ lib.optional cfg.redis.createLocally "redis-glitchtip.service"; + + inherit environment; + }; + + commonServiceConfig = { + User = cfg.user; + Group = cfg.group; + RuntimeDirectory = "glitchtip"; + StateDirectory = "glitchtip"; + EnvironmentFile = cfg.environmentFiles; + WorkingDirectory = "${pkg}/lib/glitchtip"; + + # hardening + AmbientCapabilities = ""; + CapabilityBoundingSet = [ "" ]; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = [ "AF_INET AF_INET6 AF_UNIX" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + "~@resources" + ]; + UMask = "0077"; + }; + in + { + glitchtip = commonService // { + description = "GlitchTip"; + + preStart = '' + ${lib.getExe pkg} migrate + ''; + + serviceConfig = commonServiceConfig // { + ExecStart = '' + ${lib.getExe python.pkgs.gunicorn} \ + --bind=${cfg.listenAddress}:${toString cfg.port} \ + ${lib.concatStringsSep " " cfg.gunicorn.extraArgs} \ + glitchtip.wsgi + ''; + }; + }; + + glitchtip-worker = commonService // { + description = "GlitchTip Job Runner"; + + serviceConfig = commonServiceConfig // { + ExecStart = '' + ${lib.getExe python.pkgs.celery} \ + -A glitchtip worker \ + -B -s /run/glitchtip/celerybeat-schedule \ + ${lib.concatStringsSep " " cfg.celery.extraArgs} + ''; + }; + }; + }; + + services.postgresql = lib.mkIf cfg.database.createLocally { + enable = true; + ensureDatabases = [ "glitchtip" ]; + ensureUsers = [ + { + name = "glitchtip"; + ensureDBOwnership = true; + } + ]; + }; + + services.redis.servers.glitchtip.enable = cfg.redis.createLocally; + + users.users = lib.mkIf (cfg.user == "glitchtip") { + glitchtip = { + home = "/var/lib/glitchtip"; + group = cfg.group; + extraGroups = lib.optionals cfg.redis.createLocally [ "redis-glitchtip" ]; + isSystemUser = true; + }; + }; + + users.groups = lib.mkIf (cfg.group == "glitchtip") { glitchtip = { }; }; + + environment.systemPackages = + let + glitchtip-manage = pkgs.writeShellScriptBin "glitchtip-manage" '' + set -o allexport + ${lib.toShellVars environment} + ${lib.concatMapStringsSep "\n" (f: "source ${f}") cfg.environmentFiles} + ${config.security.wrapperDir}/sudo -E -u ${cfg.user} ${lib.getExe pkg} "$@" + ''; + in + [ glitchtip-manage ]; + }; +} +