From 69b5cae897fe071f838a72cf18f86e34eff3cdf3 Mon Sep 17 00:00:00 2001 From: Moraxyc Date: Wed, 11 Dec 2024 15:26:33 +0800 Subject: [PATCH] nixos/nezha: init (cherry picked from commit b5916099b2d69864b03bb47e1192f543dcc3260a) --- nixos/modules/module-list.nix | 1 + nixos/modules/services/monitoring/nezha.nix | 179 ++++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 nixos/modules/services/monitoring/nezha.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 3963215b813b..83a7574f5a52 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1049,6 +1049,7 @@ ./services/monitoring/nagios.nix ./services/monitoring/netdata.nix ./services/monitoring/nezha-agent.nix + ./services/monitoring/nezha.nix ./services/monitoring/ocsinventory-agent.nix ./services/monitoring/opentelemetry-collector.nix ./services/monitoring/osquery.nix diff --git a/nixos/modules/services/monitoring/nezha.nix b/nixos/modules/services/monitoring/nezha.nix new file mode 100644 index 000000000000..b4ed76b031e7 --- /dev/null +++ b/nixos/modules/services/monitoring/nezha.nix @@ -0,0 +1,179 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.nezha; + + # nezha uses yaml as the configuration file format. + # Since we need to use jq to update the content, so here we generate json + settingsFormat = pkgs.formats.json { }; + configFile = settingsFormat.generate "config.json" cfg.settings; +in +{ + meta.maintainers = with lib.maintainers; [ moraxyc ]; + options = { + services.nezha = { + enable = lib.mkEnableOption "Nezha Monitoring"; + + package = lib.mkPackageOption pkgs "nezha" { }; + + debug = lib.mkEnableOption "verbose log"; + + settings = lib.mkOption { + description = '' + Generate to {file}`config.yaml` as a Nix attribute set. + Check the [guide](https://nezha.wiki/en_US/guide/dashboard.html) + for possible options. + ''; + type = lib.types.submodule { + freeformType = settingsFormat.type; + + options = { + listenhost = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = '' + Host on which the nezha web interface and grpc should listen. + ''; + }; + listenport = lib.mkOption { + type = lib.types.port; + default = 8008; + description = '' + Port on which the nezha web interface and grpc should listen. + ''; + }; + + }; + }; + }; + + mutableConfig = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether the config.yaml is writable by Nezha. + + If this option is disabled, changes on the web interface won't + be possible. If an config.yaml is present, it will be overwritten. + ''; + }; + + jwtSecretFile = lib.mkOption { + type = lib.types.path; + default = null; + description = '' + Path to the file containing the secret to sign web requests using JSON Web Tokens. + ''; + }; + + agentSecretFile = lib.mkOption { + type = lib.types.path; + default = null; + description = '' + Path to the file containing the secret used by agents to connect. + ''; + }; + + extraThemes = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + example = lib.literalExpression "[ pkgs.nezha-theme-nazhua ]"; + description = '' + A list of additional themes. + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + services.nezha.settings.debug = cfg.debug; + + systemd.services.nezha = { + serviceConfig = { + Restart = "on-failure"; + StateDirectory = "nezha"; + RuntimeDirectory = "nezha"; + ConfigurationDirectory = "nezha"; + WorkingDirectory = "/var/lib/nezha"; + ReadWritePaths = [ + "/var/lib/nezha" + "/etc/nezha" + ]; + + LoadCredential = [ + "jwt-secret:${cfg.jwtSecretFile}" + "agent-secret:${cfg.agentSecretFile}" + ]; + + # Hardening + ProcSubset = "pid"; + DynamicUser = true; + RemoveIPC = true; + LockPersonality = true; + ProtectClock = true; + MemoryDenyWriteExecute = true; + PrivateUsers = cfg.settings.listenport >= 1024; # incompatible with CAP_NET_BIND_SERVICE + ProtectHostname = true; + RestrictSUIDSGID = true; + CapabilityBoundingSet = lib.optionalString (cfg.settings.listenport < 1024) "CAP_NET_BIND_SERVICE"; + AmbientCapabilities = lib.optionalString (cfg.settings.listenport < 1024) "CAP_NET_BIND_SERVICE"; + NoNewPrivileges = true; + PrivateTmp = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RestrictNamespaces = true; + RestrictRealtime = true; + SystemCallArchitectures = "native"; + UMask = "0066"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ] + ++ lib.optional (cfg.settings ? tsdb) "mincore"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + ]; + PrivateDevices = "yes"; + + ExecStart = + let + package = cfg.package.override { withThemes = cfg.extraThemes; }; + in + ''${lib.getExe package} -c "''${CONFIGURATION_DIRECTORY}"/config.yaml -db "''${STATE_DIRECTORY}"/sqlite.db''; + }; + enableStrictShellChecks = true; + startLimitIntervalSec = 10; + startLimitBurst = 3; + preStart = '' + cp "${configFile}" "''${RUNTIME_DIRECTORY}"/new + ${lib.getExe pkgs.jq} \ + --arg jwt_secret "$(<"''${CREDENTIALS_DIRECTORY}"/jwt-secret)" \ + --arg agent_secret "$(<"''${CREDENTIALS_DIRECTORY}"/agent-secret)" \ + '. + { jwtsecretkey: $jwt_secret, agentsecretkey: $agent_secret }' \ + < "''${RUNTIME_DIRECTORY}"/new > "''${RUNTIME_DIRECTORY}"/tmp + mv "''${RUNTIME_DIRECTORY}"/tmp "''${RUNTIME_DIRECTORY}"/new + + ${lib.optionalString cfg.mutableConfig '' + [ -e "''${CONFIGURATION_DIRECTORY}"/config.yaml ] && \ + ${lib.getExe pkgs.yj} < "''${CONFIGURATION_DIRECTORY}"/config.yaml > "''${RUNTIME_DIRECTORY}"/old && \ + ${lib.getExe pkgs.jq} -s '.[0] * .[1]' \ + "''${RUNTIME_DIRECTORY}"/old "''${RUNTIME_DIRECTORY}"/new > "''${RUNTIME_DIRECTORY}"/tmp + [ -e "''${RUNTIME_DIRECTORY}"/old ] && rm "''${RUNTIME_DIRECTORY}"/old + [ -e "''${RUNTIME_DIRECTORY}"/tmp ] && mv "''${RUNTIME_DIRECTORY}"/tmp "''${RUNTIME_DIRECTORY}"/new + ''} + mv "''${RUNTIME_DIRECTORY}"/new "''${CONFIGURATION_DIRECTORY}"/config.yaml + ''; + wantedBy = [ "multi-user.target" ]; + }; + }; +}