12{3 config,4 lib,5 pkgs,6 utils,7 ...8}:9let10 inherit (lib)11 mkEnableOption12 mkIf13 mkOption14 mkPackageOption15 types16 ;17 inherit (lib.lists) optionals;18 cfg = config.services.fleet-usbd;19 devicePath = "/dev/disk/by-label/${cfg.label}";20 deviceUnit = "${utils.escapeSystemdPath devicePath}.device";21in22{23 options.services.fleet-usbd = {24 enable = mkEnableOption "fleet USB update daemon";25 package = mkPackageOption pkgs "fleet-usbd" { };26 label = mkOption {27 type = types.str;28 default = "FLEETUSBD";29 description = ''30 Volume label of the update stick, matching the label passed to31 `fleet usbd write`. FAT32 labels are at most 11 characters, uppercase.32 '';33 };34 signPublicKeys = mkOption {35 type = types.listOf types.str;36 description = ''37 Nix public keys the update closure is signed with38 (the counterparts of `fleet usbd write --sign-key`).39 '';40 };41 hook = mkOption {42 type = types.nullOr types.path;43 default = null;44 description = ''45 User feedback script, executed on update lifecycle events with the46 event name as the first argument: copying, switching, done, noop,47 failed <error>.48 '';49 };50 nk3Wink = mkOption {51 type = types.bool;52 default = false;53 description = ''54 Blink connected Nitrokey 3 devices while an update is running.55 Blinking stopped and the machine rebooted - update succeeded;56 blinking stopped without a reboot - update failed.57 '';58 };59 logRecipientFile = mkOption {60 type = types.nullOr types.path;61 default = null;62 description = ''63 Armored OpenPGP public key. When set, the daemon dumps the journal64 of the last 48 hours onto the stick after every run, encrypted to65 this key.66 '';67 };68 extraArgs = mkOption {69 type = types.listOf types.str;70 default = [ ];71 description = "Extra arguments for the fleet-usbd invocation.";72 };73 };7475 config = mkIf cfg.enable {76 nix.settings.trusted-public-keys = cfg.signPublicKeys;7778 systemd.services.fleet-usbd = {79 description = "Fleet USB update daemon";80 wantedBy = [ deviceUnit ];81 after = [82 deviceUnit83 "local-fs.target"84 ];85 path = [86 pkgs.gnupg87 config.systemd.package88 ];89 serviceConfig = {90 Type = "oneshot";91 ExecStartPre = "-${pkgs.dosfstools}/bin/fsck.vfat -a ${devicePath}";92 ExecStart = utils.escapeSystemdExecArgs (93 [94 (lib.getExe cfg.package)95 "--device"96 devicePath97 ]98 ++ optionals (cfg.hook != null) [99 "--hook"100 cfg.hook101 ]102 ++ optionals cfg.nk3Wink [ "--nk3-wink" ]103 ++ optionals (cfg.logRecipientFile != null) [104 "--log-recipient-file"105 cfg.logRecipientFile106 ]107 ++ cfg.extraArgs108 );109 };110 unitConfig = {111 X-RestartIfChanged = false;112 X-StopIfChanged = false;113 };114 };115 };116}