self: { config, lib, pkgs, ... }: let cfg = config.services.nix-ota-agent; inherit (lib) mkEnableOption mkOption mkIf types; in { options.services.nix-ota-agent = { enable = mkEnableOption "nix-ota device agent"; package = mkOption { type = types.package; default = self.packages.${pkgs.system}.nix-ota-agent; }; server = mkOption { type = types.str; example = "https://ota.example.com"; }; channel = mkOption { type = types.str; default = "prod"; }; deviceId = mkOption { type = types.str; example = "device-001"; }; publicKey = mkOption { type = types.nullOr types.str; default = null; description = "Base64-encoded ed25519 verifying key. The agent will reject manifests not signed by the matching private key. Mutually exclusive with publicKeyFile."; }; publicKeyFile = mkOption { type = types.nullOr types.path; default = null; description = "Path to a file containing the base64-encoded verifying key. Use this if you need to write the key at runtime (e.g. from an orchestration system)."; }; interval = mkOption { type = types.int; default = 60; }; healthCmd = mkOption { type = types.nullOr types.str; default = null; }; cacheUrl = mkOption { type = types.str; description = "Substituter URL added to nix.settings.substituters so `nix copy` can fetch from it."; }; cachePublicKey = mkOption { type = types.str; description = "Trusted public key of the binary cache (the one that signs store paths)."; }; }; config = mkIf cfg.enable { assertions = [{ assertion = (cfg.publicKey != null) != (cfg.publicKeyFile != null); message = "services.nix-ota-agent: set exactly one of publicKey or publicKeyFile."; }]; nix.settings = { substituters = [ cfg.cacheUrl ]; trusted-public-keys = [ cfg.cachePublicKey ]; experimental-features = [ "nix-command" "flakes" ]; }; environment.etc."nix-ota/public.key" = lib.mkIf (cfg.publicKey != null) { text = cfg.publicKey; }; systemd.services.nix-ota-agent = { description = "nix-ota device agent (oneshot)"; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; serviceConfig = { Type = "oneshot"; StateDirectory = "nix-ota"; }; environment = { NIX_OTA_SERVER = cfg.server; NIX_OTA_CHANNEL = cfg.channel; NIX_OTA_DEVICE_ID = cfg.deviceId; NIX_OTA_PUBLIC_KEY_FILE = if cfg.publicKeyFile != null then toString cfg.publicKeyFile else "/etc/nix-ota/public.key"; NIX_OTA_STATE_DIR = "/var/lib/nix-ota"; } // lib.optionalAttrs (cfg.healthCmd != null) { NIX_OTA_HEALTH_CMD = cfg.healthCmd; }; script = '' export PATH=${lib.makeBinPath [ pkgs.nix pkgs.systemd pkgs.coreutils pkgs.bash ]}:$PATH exec ${cfg.package}/bin/nix-ota-agent --once ''; }; systemd.timers.nix-ota-agent = { wantedBy = [ "timers.target" ]; timerConfig = { OnBootSec = "1min"; OnUnitActiveSec = "${toString cfg.interval}s"; Unit = "nix-ota-agent.service"; }; }; }; }