{ pkg, rev }: { config, lib, ... }: let cfg = config.services.noisebell; bin = "${pkg}/bin/noisebell"; in { options.services.noisebell = { enable = lib.mkEnableOption "noisebell GPIO door monitor"; gpioPin = lib.mkOption { type = lib.types.ints.unsigned; default = 17; description = "GPIO pin number to monitor."; }; debounceMs = lib.mkOption { type = lib.types.ints.positive; default = 50; description = "Debounce delay in milliseconds."; }; port = lib.mkOption { type = lib.types.port; default = 8080; description = "HTTP port for the status endpoint."; }; endpointUrl = lib.mkOption { type = lib.types.str; description = "Webhook endpoint URL to POST state changes to."; }; apiKeyFile = lib.mkOption { type = lib.types.path; description = "Path to a file containing the outbound API key for the cache endpoint."; }; inboundApiKeyFile = lib.mkOption { type = lib.types.path; description = "Path to a file containing the inbound API key for authenticating GET requests."; }; retryAttempts = lib.mkOption { type = lib.types.ints.unsigned; default = 3; description = "Number of retries after a failed webhook POST."; }; retryBaseDelaySecs = lib.mkOption { type = lib.types.ints.positive; default = 1; description = "Base delay in seconds for exponential backoff between retries."; }; httpTimeoutSecs = lib.mkOption { type = lib.types.ints.positive; default = 10; description = "Timeout in seconds for outbound HTTP requests to the webhook endpoint."; }; bindAddress = lib.mkOption { type = lib.types.str; default = "0.0.0.0"; description = "Address to bind the HTTP server to."; }; activeLow = lib.mkOption { type = lib.types.bool; default = true; description = "Whether a low GPIO level means open. Set to false if your sensor wiring is inverted."; }; restartDelaySecs = lib.mkOption { type = lib.types.ints.positive; default = 5; description = "Seconds to wait before systemd restarts the service on failure."; }; watchdogSecs = lib.mkOption { type = lib.types.ints.positive; default = 30; description = "Watchdog timeout in seconds. The service is restarted if it fails to notify systemd within this interval."; }; }; config = lib.mkIf cfg.enable { systemd.services.noisebell = { description = "Noisebell GPIO door monitor"; wantedBy = [ "multi-user.target" ]; after = [ "network-online.target" "tailscaled.service" ]; wants = [ "network-online.target" ]; environment = { NOISEBELL_GPIO_PIN = toString cfg.gpioPin; NOISEBELL_DEBOUNCE_MS = toString cfg.debounceMs; NOISEBELL_PORT = toString cfg.port; NOISEBELL_RETRY_ATTEMPTS = toString cfg.retryAttempts; NOISEBELL_RETRY_BASE_DELAY_SECS = toString cfg.retryBaseDelaySecs; NOISEBELL_HTTP_TIMEOUT_SECS = toString cfg.httpTimeoutSecs; NOISEBELL_ENDPOINT_URL = cfg.endpointUrl; NOISEBELL_BIND_ADDRESS = cfg.bindAddress; NOISEBELL_ACTIVE_LOW = if cfg.activeLow then "true" else "false"; NOISEBELL_COMMIT = rev; RUST_LOG = "info"; }; script = '' export NOISEBELL_API_KEY="$(cat ${cfg.apiKeyFile})" export NOISEBELL_INBOUND_API_KEY="$(cat ${cfg.inboundApiKeyFile})" exec ${bin} ''; serviceConfig = { Type = "notify"; NotifyAccess = "all"; WatchdogSec = cfg.watchdogSecs; Restart = "on-failure"; RestartSec = cfg.restartDelaySecs; }; }; }; }