main
1{
2 den,
3 lib,
4 ...
5}: {
6 den.aspects.services.forgejo.runner = {
7 includes = [den.aspects.services.podman];
8 settings.host = {
9 instances = lib.mkOption {
10 type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
11 options = {
12 name = lib.mkOption {
13 type = lib.types.str;
14 default = name;
15 };
16 servers = lib.mkOption {
17 type = lib.types.attrsOf (lib.types.submodule {
18 options = {
19 url = lib.mkOption {
20 type = lib.types.str;
21 };
22 uuid = lib.mkOption {
23 type = lib.types.str;
24 };
25 tokenFileAged = lib.mkOption {
26 type = lib.types.path;
27 };
28 labels = lib.mkOption {
29 type = lib.types.listOf lib.types.str;
30 description = "Extra labels used for this server.";
31 default = [];
32 };
33 };
34 });
35 default = {};
36 };
37 labels = lib.mkOption {
38 type = lib.types.listOf lib.types.str;
39 default = [];
40 };
41 cacheProxyPort = lib.mkOption {
42 type = lib.types.port;
43 default = 4000;
44 description = "Port of the internal cache proxy. Must be unique per instance, as each runner binds it on the same host.";
45 };
46 resourcesLimit = {
47 capacity = lib.mkOption {
48 type = lib.types.ints.positive;
49 default = 1;
50 description = "Execute how many tasks concurrently at the same time.";
51 };
52 cpus = lib.mkOption {
53 type = lib.types.ints.positive;
54 default = 1;
55 description = "CPU cores usage limit per container";
56 };
57 memory = lib.mkOption {
58 type = lib.types.str;
59 default = "1g";
60 description = "Memory usage limit per container";
61 };
62 };
63 extraEnvironments = lib.mkOption {
64 type = lib.types.attrsOf lib.types.str;
65 default = {};
66 };
67 extraSettings = lib.mkOption {
68 type = lib.types.attrsOf lib.types.anything;
69 default = {};
70 };
71 };
72 }));
73 default = {};
74 };
75 };
76
77 persist = {
78 directoies = [
79 {
80 directory = "/var/lib/private/forgejo-runner";
81 user = "nobody";
82 group = "nogroup";
83 mode = "0700";
84 }
85 ];
86 };
87
88 nixos = {
89 host,
90 config,
91 ...
92 }: let
93 cfg = host.settings.services.forgejo.runner;
94 mkServerTokenSecretName = instance: server: "forgejo-runner-${instance}-${server}-token";
95 cacheProxyPorts = lib.mapAttrsToList (_: instanceCfg: instanceCfg.cacheProxyPort) cfg.instances;
96
97 matchPodmanNetwork =
98 if config.networking.nftables.enable
99 then "podman*"
100 else "podman+";
101 in {
102 # Docker runners with cache actions need their "br-*" bridges trusted.
103 # See https://forgejo.org/docs/next/admin/actions/runner-installation/#nixos
104 networking.firewall.trustedInterfaces =
105 if (config.networking.nftables.enable)
106 then ["br-*"]
107 else ["br-+"];
108
109 # The cache proxy must be reachable from job containers on podman bridges.
110 networking.firewall.interfaces."${matchPodmanNetwork}".allowedTCPPorts = cacheProxyPorts;
111
112 services.forgejo-runner.instances =
113 lib.mapAttrs (instance: instanceCfg: {
114 enable = true;
115
116 settings = lib.mkMerge [
117 {
118 runner = {
119 labels = lib.unique (
120 instanceCfg.labels
121 ++ (
122 lib.concatLists
123 (lib.mapAttrsToList
124 (_: serverCfg: serverCfg.labels)
125 instanceCfg.servers)
126 )
127 );
128 capacity = instanceCfg.resourcesLimit.capacity;
129 };
130 server.connections =
131 lib.mapAttrs (server: serverCfg: {
132 inherit (serverCfg) url uuid;
133 })
134 instanceCfg.servers;
135 cache = {
136 enabled = true;
137 # See https://forgejo.org/docs/latest/user/actions/advanced-features/#cache
138 # ONLY for podman backend
139 proxy_port = instanceCfg.cacheProxyPort;
140 actions_cache_url_override = lib.mkDefault "http://host.containers.internal:${toString instanceCfg.cacheProxyPort}";
141 };
142 container = {
143 enable_ipv6 = true;
144 options = lib.concatStringsSep " " [
145 "--cpus=${toString instanceCfg.resourcesLimit.cpus}"
146 "--memory=${toString instanceCfg.resourcesLimit.memory}"
147 "--cap-add sys_admin"
148 "--cap-add mknod"
149 "--device /dev/fuse"
150 ];
151 };
152 }
153 instanceCfg.extraSettings
154 ];
155
156 secrets = {
157 server.connections =
158 lib.mapAttrs (server: _: {
159 token_url = config.vaultix.secrets.${mkServerTokenSecretName instance server}.path;
160 })
161 instanceCfg.servers;
162 };
163 })
164 cfg.instances;
165
166 vaultix.secrets = lib.mergeAttrsList (
167 lib.mapAttrsToList (instance: instanceCfg:
168 lib.mapAttrs' (
169 server: serverCfg:
170 lib.nameValuePair (mkServerTokenSecretName instance server) {
171 file = serverCfg.tokenFileAged;
172 }
173 )
174 instanceCfg.servers)
175 cfg.instances
176 );
177 };
178 };
179}