|
| 1 | +{ |
| 2 | + config, |
| 3 | + lib, |
| 4 | + pkgs, |
| 5 | + ... |
| 6 | +}: |
| 7 | +let |
| 8 | + inherit (lib) |
| 9 | + getExe |
| 10 | + literalExpression |
| 11 | + maintainers |
| 12 | + mkEnableOption |
| 13 | + mkIf |
| 14 | + mkOption |
| 15 | + ; |
| 16 | + inherit (lib.strings) concatStringsSep escapeShellArgs replaceString; |
| 17 | + inherit (lib.types) |
| 18 | + bool |
| 19 | + listOf |
| 20 | + package |
| 21 | + str |
| 22 | + ; |
| 23 | + |
| 24 | + cfg = config.services.${unitName}; |
| 25 | + |
| 26 | + unitName = "update-flake-inputs"; |
| 27 | +in |
| 28 | +{ |
| 29 | + meta.maintainers = [ |
| 30 | + maintainers.l0b0 |
| 31 | + ]; |
| 32 | + |
| 33 | + options.services.${unitName} = { |
| 34 | + enable = mkEnableOption "Whether to update Nix flake inputs on a schedule"; |
| 35 | + |
| 36 | + directories = mkOption { |
| 37 | + type = listOf str; |
| 38 | + default = [ ]; |
| 39 | + example = [ |
| 40 | + "/home/user/foo" |
| 41 | + "/home/user/my projects/bar" |
| 42 | + ]; |
| 43 | + description = "Absolute paths of directories to perform updates in"; |
| 44 | + }; |
| 45 | + |
| 46 | + afterUpdateCommands = mkOption { |
| 47 | + type = listOf str; |
| 48 | + default = [ ]; |
| 49 | + example = [ |
| 50 | + "NIX_ABORT_ON_WARN=true nix flake check" |
| 51 | + "nix develop --ignore-env --command pre-commit run --all-files" |
| 52 | + "git commit --message=\"build: Update Nix flake input '$${input}'\" -- flake.lock" |
| 53 | + ]; |
| 54 | + description = '' |
| 55 | + Commands to run after the update. |
| 56 | + The variable {env}`input` can be referenced in this script to refer to the flake input name, |
| 57 | + and the variable {env}`PWD` to refer to the flake directory. |
| 58 | + ''; |
| 59 | + }; |
| 60 | + |
| 61 | + afterUpdateCommandsDependencies = mkOption { |
| 62 | + type = listOf package; |
| 63 | + default = [ ]; |
| 64 | + example = [ |
| 65 | + literalExpression |
| 66 | + "pkgs.firefox" |
| 67 | + ]; |
| 68 | + description = '' |
| 69 | + Packages required by {option}`afterUpdateCommands`. |
| 70 | + ''; |
| 71 | + }; |
| 72 | + |
| 73 | + onCalendar = mkOption { |
| 74 | + type = str; |
| 75 | + default = "daily"; |
| 76 | + example = "04:40"; |
| 77 | + description = '' |
| 78 | + How often or when update occurs. |
| 79 | +
|
| 80 | + The format is described in |
| 81 | + {manpage}`systemd.time(7)`. |
| 82 | + ''; |
| 83 | + }; |
| 84 | + |
| 85 | + randomizedDelaySec = mkOption { |
| 86 | + default = "0"; |
| 87 | + type = str; |
| 88 | + example = "45 minutes"; |
| 89 | + description = '' |
| 90 | + Add a randomized delay before each run. |
| 91 | + The delay will be chosen between zero and this value. |
| 92 | + This value must be a time span in the format specified by |
| 93 | + {manpage}`systemd.time(7)` |
| 94 | + ''; |
| 95 | + }; |
| 96 | + |
| 97 | + fixedRandomDelay = mkOption { |
| 98 | + default = false; |
| 99 | + type = bool; |
| 100 | + example = true; |
| 101 | + description = '' |
| 102 | + Make the randomized delay consistent between runs. |
| 103 | + This reduces the jitter between automatic updates. |
| 104 | + See {option}`randomizedDelaySec` for configuring the randomized delay. |
| 105 | + ''; |
| 106 | + }; |
| 107 | + |
| 108 | + persistent = mkOption { |
| 109 | + default = true; |
| 110 | + type = bool; |
| 111 | + example = false; |
| 112 | + description = '' |
| 113 | + Takes a boolean argument. If true, the time when the service |
| 114 | + unit was last triggered is stored on disk. When the timer is |
| 115 | + activated, the service unit is triggered immediately if it |
| 116 | + would have been triggered at least once during the time when |
| 117 | + the timer was inactive. Such triggering is nonetheless |
| 118 | + subject to the delay imposed by RandomizedDelaySec=. This is |
| 119 | + useful to catch up on missed runs of the service when the |
| 120 | + system was powered down. |
| 121 | + ''; |
| 122 | + }; |
| 123 | + }; |
| 124 | + |
| 125 | + config = mkIf cfg.enable { |
| 126 | + assertions = [ |
| 127 | + { |
| 128 | + assertion = cfg.directories != [ ]; |
| 129 | + message = "You must specify some directories to act on."; |
| 130 | + } |
| 131 | + ]; |
| 132 | + |
| 133 | + systemd.user = |
| 134 | + let |
| 135 | + Description = "Update Nix flake inputs"; |
| 136 | + in |
| 137 | + { |
| 138 | + services.${unitName} = |
| 139 | + let |
| 140 | + updateFlakeInputs = pkgs.writeShellApplication { |
| 141 | + name = unitName; |
| 142 | + bashOptions = [ ]; |
| 143 | + runtimeInputs = [ |
| 144 | + pkgs.gitMinimal |
| 145 | + pkgs.jq |
| 146 | + ] |
| 147 | + ++ cfg.afterUpdateCommandsDependencies; |
| 148 | + text = |
| 149 | + let |
| 150 | + script = builtins.readFile ./update.bash; |
| 151 | + afterUpdateCommandLine = concatStringsSep " && " ( |
| 152 | + if cfg.afterUpdateCommands == [ ] then [ "true" ] else cfg.afterUpdateCommands |
| 153 | + ); |
| 154 | + in |
| 155 | + replaceString "@afterUpdateCommandLine@" afterUpdateCommandLine script; |
| 156 | + }; |
| 157 | + in |
| 158 | + { |
| 159 | + Unit = { |
| 160 | + inherit Description; |
| 161 | + Documentation = [ "man:nix3-flake(1)" ]; |
| 162 | + |
| 163 | + After = [ "network-online.target" ]; |
| 164 | + Wants = [ "network-online.target" ]; |
| 165 | + |
| 166 | + X-StopOnRemoval = false; |
| 167 | + X-RestartIfChanged = false; |
| 168 | + }; |
| 169 | + |
| 170 | + Service = { |
| 171 | + ExecStart = '' |
| 172 | + ${getExe updateFlakeInputs} ${escapeShellArgs cfg.directories} |
| 173 | + ''; |
| 174 | + Type = "oneshot"; |
| 175 | + }; |
| 176 | + }; |
| 177 | + |
| 178 | + timers.${unitName} = { |
| 179 | + Install.WantedBy = [ "timers.target" ]; |
| 180 | + |
| 181 | + Timer = { |
| 182 | + FixedRandomDelay = cfg.fixedRandomDelay; |
| 183 | + OnCalendar = cfg.onCalendar; |
| 184 | + Persistent = cfg.persistent; |
| 185 | + RandomizedDelaySec = cfg.randomizedDelaySec; |
| 186 | + Unit = "${unitName}.service"; |
| 187 | + }; |
| 188 | + |
| 189 | + Unit = { inherit Description; }; |
| 190 | + }; |
| 191 | + }; |
| 192 | + }; |
| 193 | + |
| 194 | +} |
0 commit comments