pkg: { config, lib, ... }: let cfg = config.services.noisebell-cache; bin = "${pkg}/bin/noisebell-cache"; in { options.services.noisebell-cache = { enable = lib.mkEnableOption "noisebell cache service"; domain = lib.mkOption { type = lib.types.str; description = "Domain for the Caddy virtual host."; }; piAddress = lib.mkOption { type = lib.types.str; description = "Address of the Pi (e.g. http://noisebell:80)."; }; piApiKeyFile = lib.mkOption { type = lib.types.path; description = "Path to file containing API key for authenticating to Pi GET endpoints."; }; inboundApiKeyFile = lib.mkOption { type = lib.types.path; description = "Path to file containing API key for the cache's inbound webhook."; }; port = lib.mkOption { type = lib.types.port; default = 3000; }; statusPollIntervalSecs = lib.mkOption { type = lib.types.ints.positive; default = 60; }; infoPollIntervalSecs = lib.mkOption { type = lib.types.ints.positive; default = 300; }; offlineThreshold = lib.mkOption { type = lib.types.ints.positive; default = 3; }; retryAttempts = lib.mkOption { type = lib.types.ints.unsigned; default = 3; }; retryBaseDelaySecs = lib.mkOption { type = lib.types.ints.positive; default = 1; }; httpTimeoutSecs = lib.mkOption { type = lib.types.ints.positive; default = 10; }; dataDir = lib.mkOption { type = lib.types.str; default = "/var/lib/noisebell-cache"; }; outboundWebhooks = lib.mkOption { type = lib.types.listOf (lib.types.submodule { options = { url = lib.mkOption { type = lib.types.str; }; secretFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; }; }; }); default = []; }; }; config = lib.mkIf cfg.enable { users.users.noisebell-cache = { isSystemUser = true; group = "noisebell-cache"; }; users.groups.noisebell-cache = {}; services.caddy.virtualHosts.${cfg.domain}.extraConfig = '' redir / https://git.extremist.software/jet/noisebell 302 reverse_proxy localhost:${toString cfg.port} ''; systemd.services.noisebell-cache = let webhookExports = lib.concatImapStringsSep "\n" (i: wh: let idx = toString (i - 1); in ''export NOISEBELL_CACHE_WEBHOOK_${idx}_URL="${wh.url}"'' + lib.optionalString (wh.secretFile != null) '' export NOISEBELL_CACHE_WEBHOOK_${idx}_SECRET="$(cat ${wh.secretFile})"'' ) cfg.outboundWebhooks; in { description = "Noisebell cache service"; wantedBy = [ "multi-user.target" ]; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; environment = { NOISEBELL_CACHE_PORT = toString cfg.port; NOISEBELL_CACHE_PI_ADDRESS = cfg.piAddress; NOISEBELL_CACHE_DATA_DIR = cfg.dataDir; NOISEBELL_CACHE_STATUS_POLL_INTERVAL_SECS = toString cfg.statusPollIntervalSecs; NOISEBELL_CACHE_INFO_POLL_INTERVAL_SECS = toString cfg.infoPollIntervalSecs; NOISEBELL_CACHE_OFFLINE_THRESHOLD = toString cfg.offlineThreshold; NOISEBELL_CACHE_RETRY_ATTEMPTS = toString cfg.retryAttempts; NOISEBELL_CACHE_RETRY_BASE_DELAY_SECS = toString cfg.retryBaseDelaySecs; NOISEBELL_CACHE_HTTP_TIMEOUT_SECS = toString cfg.httpTimeoutSecs; RUST_LOG = "info"; }; script = '' export NOISEBELL_CACHE_INBOUND_API_KEY="$(cat ${cfg.inboundApiKeyFile})" export NOISEBELL_CACHE_PI_API_KEY="$(cat ${cfg.piApiKeyFile})" ${webhookExports} exec ${bin} ''; serviceConfig = { Type = "simple"; Restart = "on-failure"; RestartSec = 5; User = "noisebell-cache"; Group = "noisebell-cache"; StateDirectory = "noisebell-cache"; NoNewPrivileges = true; ProtectSystem = "strict"; ProtectHome = true; PrivateTmp = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectControlGroups = true; RestrictSUIDSGID = true; ReadWritePaths = [ cfg.dataDir ]; }; }; }; }