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 runtimePackages = mkOption {
65 type = with types; listOf package;
66 description = "Packages available to renovate.";
67 default = [ ];
68 };
69 validateSettings = mkOption {
70 type = types.bool;
71 default = true;
72 description = "Whether to run renovate's config validator on the built configuration.";
73 };
74 settings = mkOption {
75 type = json.type;
76 default = { };
77 example = {
78 platform = "gitea";
79 endpoint = "https://git.example.com";
80 gitAuthor = "Renovate <renovate@example.com>";
81 };
82 description = ''
83 Renovate's global configuration.
84 If you want to pass secrets to renovate, please use {option}`services.renovate.credentials` for that.
85 '';
86 };
87 };
88
89 config = mkIf cfg.enable {
90 services.renovate.settings = {
91 cacheDir = "/var/cache/renovate";
92 baseDir = "/var/lib/renovate";
93 };
94
95 systemd.services.renovate = {
96 description = "Renovate dependency updater";
97 documentation = [ "https://docs.renovatebot.com/" ];
98 after = [ "network.target" ];
99 startAt = lib.optional (cfg.schedule != null) cfg.schedule;
100 path = [
101 config.systemd.package
102 pkgs.git
103 ] ++ cfg.runtimePackages;
104
105 serviceConfig = {
106 User = "renovate";
107 Group = "renovate";
108 DynamicUser = true;
109 LoadCredential = lib.mapAttrsToList (name: value: "SECRET-${name}:${value}") cfg.credentials;
110 CacheDirectory = "renovate";
111 StateDirectory = "renovate";
112
113 # Hardening
114 CapabilityBoundingSet = [ "" ];
115 DeviceAllow = [ "" ];
116 LockPersonality = true;
117 PrivateDevices = true;
118 PrivateUsers = true;
119 ProcSubset = "pid";
120 ProtectClock = true;
121 ProtectControlGroups = true;
122 ProtectHome = true;
123 ProtectHostname = true;
124 ProtectKernelLogs = true;
125 ProtectKernelModules = true;
126 ProtectKernelTunables = true;
127 ProtectProc = "invisible";
128 RestrictAddressFamilies = [
129 "AF_INET"
130 "AF_INET6"
131 "AF_UNIX"
132 ];
133 RestrictNamespaces = true;
134 RestrictRealtime = true;
135 SystemCallArchitectures = "native";
136 UMask = "0077";
137 };
138
139 script = ''
140 ${lib.concatStringsSep "\n" (
141 builtins.map (name: ''
142 ${name}="$(systemd-creds cat 'SECRET-${name}')"
143 export ${name}
144 '') (lib.attrNames cfg.credentials)
145 )}
146 exec ${lib.escapeShellArg (lib.getExe cfg.package)}
147 '';
148
149 environment = {
150 RENOVATE_CONFIG_FILE = generateConfig "renovate-config.json" cfg.settings;
151 HOME = "/var/lib/renovate";
152 };
153 };
154 };
155}