1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 inherit (lib)
9 mkEnableOption
10 mkPackageOption
11 mkOption
12 types
13 mkIf
14 ;
15 json = pkgs.formats.json { };
16 cfg = config.services.renovate;
17 generateValidatedConfig =
18 name: value:
19 pkgs.callPackage (
20 { runCommand, jq }:
21 runCommand name
22 {
23 nativeBuildInputs = [
24 jq
25 cfg.package
26 ];
27 value = builtins.toJSON value;
28 passAsFile = [ "value" ];
29 preferLocalBuild = true;
30 }
31 ''
32 jq . "$valuePath"> $out
33 renovate-config-validator $out
34 ''
35 ) { };
36 generateConfig = if cfg.validateSettings then generateValidatedConfig else json.generate;
37in
38{
39 meta.maintainers = with lib.maintainers; [
40 marie
41 natsukium
42 ];
43
44 options.services.renovate = {
45 enable = mkEnableOption "renovate";
46 package = mkPackageOption pkgs "renovate" { };
47 schedule = mkOption {
48 type = with types; nullOr str;
49 description = "How often to run renovate. See {manpage}`systemd.time(7)` for the format.";
50 example = "*:0/10";
51 default = null;
52 };
53 credentials = mkOption {
54 type = with types; attrsOf path;
55 description = ''
56 Allows configuring environment variable credentials for renovate, read from files.
57 This should always be used for passing confidential data to renovate.
58 '';
59 example = {
60 RENOVATE_TOKEN = "/etc/renovate/token";
61 };
62 default = { };
63 };
64 environment = mkOption {
65 type =
66 with types;
67 attrsOf (
68 nullOr (oneOf [
69 str
70 path
71 package
72 ])
73 );
74 description = ''
75 Extra environment variables to export to the Renovate process
76 from the systemd unit configuration.
77
78 See <https://docs.renovatebot.com/config-overview> for available environment variables.
79 '';
80 example = {
81 LOG_LEVEL = "debug";
82 };
83 default = { };
84 };
85 runtimePackages = mkOption {
86 type = with types; listOf package;
87 description = "Packages available to renovate.";
88 default = [ ];
89 };
90 validateSettings = mkOption {
91 type = types.bool;
92 default = true;
93 description = "Whether to run renovate's config validator on the built configuration.";
94 };
95 settings = mkOption {
96 type = json.type;
97 default = { };
98 example = {
99 platform = "gitea";
100 endpoint = "https://git.example.com";
101 gitAuthor = "Renovate <renovate@example.com>";
102 };
103 description = ''
104 Renovate's global configuration.
105 If you want to pass secrets to renovate, please use {option}`services.renovate.credentials` for that.
106
107 See <https://docs.renovatebot.com/config-overview> for available settings.
108 '';
109 };
110 };
111
112 config = mkIf cfg.enable {
113 services.renovate = {
114 settings = {
115 cacheDir = "/var/cache/renovate";
116 baseDir = "/var/lib/renovate";
117 };
118 environment = {
119 RENOVATE_CONFIG_FILE = generateConfig "renovate-config.json" cfg.settings;
120 HOME = "/var/lib/renovate";
121 };
122 };
123
124 systemd.services.renovate = {
125 description = "Renovate dependency updater";
126 documentation = [ "https://docs.renovatebot.com/" ];
127 after = [ "network.target" ];
128 startAt = lib.optional (cfg.schedule != null) cfg.schedule;
129 path = [
130 config.systemd.package
131 pkgs.git
132 ]
133 ++ cfg.runtimePackages;
134 inherit (cfg) environment;
135
136 serviceConfig = {
137 User = "renovate";
138 Group = "renovate";
139 DynamicUser = true;
140 LoadCredential = lib.mapAttrsToList (name: value: "SECRET-${name}:${value}") cfg.credentials;
141 CacheDirectory = "renovate";
142 StateDirectory = "renovate";
143
144 # Hardening
145 CapabilityBoundingSet = [ "" ];
146 DeviceAllow = [ "" ];
147 LockPersonality = true;
148 PrivateDevices = true;
149 PrivateUsers = true;
150 ProcSubset = "pid";
151 ProtectClock = true;
152 ProtectControlGroups = true;
153 ProtectHome = true;
154 ProtectHostname = true;
155 ProtectKernelLogs = true;
156 ProtectKernelModules = true;
157 ProtectKernelTunables = true;
158 ProtectProc = "invisible";
159 RestrictAddressFamilies = [
160 "AF_INET"
161 "AF_INET6"
162 "AF_UNIX"
163 ];
164 RestrictNamespaces = true;
165 RestrictRealtime = true;
166 SystemCallArchitectures = "native";
167 UMask = "0077";
168 };
169
170 script = ''
171 ${lib.concatStringsSep "\n" (
172 builtins.map (name: ''
173 ${name}="$(systemd-creds cat 'SECRET-${name}')"
174 export ${name}
175 '') (lib.attrNames cfg.credentials)
176 )}
177 exec ${lib.escapeShellArg (lib.getExe cfg.package)}
178 '';
179 };
180 };
181}