at 25.11-pre 9.3 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 utils, 6 ... 7}: 8 9let 10 inherit (lib) 11 any 12 attrValues 13 concatStringsSep 14 escapeShellArg 15 hasInfix 16 hasSuffix 17 optionalAttrs 18 optionals 19 literalExpression 20 mapAttrs' 21 mkEnableOption 22 mkOption 23 mkPackageOption 24 mkIf 25 nameValuePair 26 types 27 ; 28 29 inherit (utils) 30 escapeSystemdPath 31 ; 32 33 cfg = config.services.gitea-actions-runner; 34 35 settingsFormat = pkgs.formats.yaml { }; 36 37 # Check whether any runner instance label requires a container runtime 38 # Empty label strings result in the upstream defined defaultLabels, which require docker 39 # https://gitea.com/gitea/act_runner/src/tag/v0.1.5/internal/app/cmd/register.go#L93-L98 40 hasDockerScheme = 41 instance: instance.labels == [ ] || any (label: hasInfix ":docker:" label) instance.labels; 42 wantsContainerRuntime = any hasDockerScheme (attrValues cfg.instances); 43 44 hasHostScheme = instance: any (label: hasSuffix ":host" label) instance.labels; 45 46 # provide shorthands for whether container runtimes are enabled 47 hasDocker = config.virtualisation.docker.enable; 48 hasPodman = config.virtualisation.podman.enable; 49 50 tokenXorTokenFile = 51 instance: 52 (instance.token == null && instance.tokenFile != null) 53 || (instance.token != null && instance.tokenFile == null); 54in 55{ 56 meta.maintainers = with lib.maintainers; [ 57 hexa 58 ]; 59 60 options.services.gitea-actions-runner = with types; { 61 package = mkPackageOption pkgs "gitea-actions-runner" { }; 62 63 instances = mkOption { 64 default = { }; 65 description = '' 66 Gitea Actions Runner instances. 67 ''; 68 type = attrsOf (submodule { 69 options = { 70 enable = mkEnableOption "Gitea Actions Runner instance"; 71 72 name = mkOption { 73 type = str; 74 example = literalExpression "config.networking.hostName"; 75 description = '' 76 The name identifying the runner instance towards the Gitea/Forgejo instance. 77 ''; 78 }; 79 80 url = mkOption { 81 type = str; 82 example = "https://forge.example.com"; 83 description = '' 84 Base URL of your Gitea/Forgejo instance. 85 ''; 86 }; 87 88 token = mkOption { 89 type = nullOr str; 90 default = null; 91 description = '' 92 Plain token to register at the configured Gitea/Forgejo instance. 93 ''; 94 }; 95 96 tokenFile = mkOption { 97 type = nullOr (either str path); 98 default = null; 99 description = '' 100 Path to an environment file, containing the `TOKEN` environment 101 variable, that holds a token to register at the configured 102 Gitea/Forgejo instance. 103 ''; 104 }; 105 106 labels = mkOption { 107 type = listOf str; 108 example = literalExpression '' 109 [ 110 # provide a debian base with nodejs for actions 111 "debian-latest:docker://node:18-bullseye" 112 # fake the ubuntu name, because node provides no ubuntu builds 113 "ubuntu-latest:docker://node:18-bullseye" 114 # provide native execution on the host 115 #"native:host" 116 ] 117 ''; 118 description = '' 119 Labels used to map jobs to their runtime environment. Changing these 120 labels currently requires a new registration token. 121 122 Many common actions require bash, git and nodejs, as well as a filesystem 123 that follows the filesystem hierarchy standard. 124 ''; 125 }; 126 settings = mkOption { 127 description = '' 128 Configuration for `act_runner daemon`. 129 See https://gitea.com/gitea/act_runner/src/branch/main/internal/pkg/config/config.example.yaml for an example configuration 130 ''; 131 132 type = types.submodule { 133 freeformType = settingsFormat.type; 134 }; 135 136 default = { }; 137 }; 138 139 hostPackages = mkOption { 140 type = listOf package; 141 default = with pkgs; [ 142 bash 143 coreutils 144 curl 145 gawk 146 gitMinimal 147 gnused 148 nodejs 149 wget 150 ]; 151 defaultText = literalExpression '' 152 with pkgs; [ 153 bash 154 coreutils 155 curl 156 gawk 157 gitMinimal 158 gnused 159 nodejs 160 wget 161 ] 162 ''; 163 description = '' 164 List of packages, that are available to actions, when the runner is configured 165 with a host execution label. 166 ''; 167 }; 168 }; 169 }); 170 }; 171 }; 172 173 config = mkIf (cfg.instances != { }) { 174 assertions = [ 175 { 176 assertion = any tokenXorTokenFile (attrValues cfg.instances); 177 message = "Instances of gitea-actions-runner can have `token` or `tokenFile`, not both."; 178 } 179 { 180 assertion = wantsContainerRuntime -> hasDocker || hasPodman; 181 message = "Label configuration on gitea-actions-runner instance requires either docker or podman."; 182 } 183 ]; 184 185 systemd.services = 186 let 187 mkRunnerService = 188 name: instance: 189 let 190 wantsContainerRuntime = hasDockerScheme instance; 191 wantsHost = hasHostScheme instance; 192 wantsDocker = wantsContainerRuntime && config.virtualisation.docker.enable; 193 wantsPodman = wantsContainerRuntime && config.virtualisation.podman.enable; 194 configFile = settingsFormat.generate "config.yaml" instance.settings; 195 in 196 nameValuePair "gitea-runner-${escapeSystemdPath name}" { 197 inherit (instance) enable; 198 description = "Gitea Actions Runner"; 199 wants = [ "network-online.target" ]; 200 after = 201 [ 202 "network-online.target" 203 ] 204 ++ optionals (wantsDocker) [ 205 "docker.service" 206 ] 207 ++ optionals (wantsPodman) [ 208 "podman.service" 209 ]; 210 wantedBy = [ 211 "multi-user.target" 212 ]; 213 environment = 214 optionalAttrs (instance.token != null) { 215 TOKEN = "${instance.token}"; 216 } 217 // optionalAttrs (wantsPodman) { 218 DOCKER_HOST = "unix:///run/podman/podman.sock"; 219 } 220 // { 221 HOME = "/var/lib/gitea-runner/${name}"; 222 }; 223 path = 224 with pkgs; 225 [ 226 coreutils 227 ] 228 ++ lib.optionals wantsHost instance.hostPackages; 229 serviceConfig = 230 { 231 DynamicUser = true; 232 User = "gitea-runner"; 233 StateDirectory = "gitea-runner"; 234 WorkingDirectory = "-/var/lib/gitea-runner/${name}"; 235 236 # gitea-runner might fail when gitea is restarted during upgrade. 237 Restart = "on-failure"; 238 RestartSec = 2; 239 240 ExecStartPre = [ 241 (pkgs.writeShellScript "gitea-register-runner-${name}" '' 242 export INSTANCE_DIR="$STATE_DIRECTORY/${name}" 243 mkdir -vp "$INSTANCE_DIR" 244 cd "$INSTANCE_DIR" 245 246 # force reregistration on changed labels 247 export LABELS_FILE="$INSTANCE_DIR/.labels" 248 export LABELS_WANTED="$(echo ${escapeShellArg (concatStringsSep "\n" instance.labels)} | sort)" 249 export LABELS_CURRENT="$(cat $LABELS_FILE 2>/dev/null || echo 0)" 250 251 if [ ! -e "$INSTANCE_DIR/.runner" ] || [ "$LABELS_WANTED" != "$LABELS_CURRENT" ]; then 252 # remove existing registration file, so that changing the labels forces a re-registration 253 rm -v "$INSTANCE_DIR/.runner" || true 254 255 # perform the registration 256 ${cfg.package}/bin/act_runner register --no-interactive \ 257 --instance ${escapeShellArg instance.url} \ 258 --token "$TOKEN" \ 259 --name ${escapeShellArg instance.name} \ 260 --labels ${escapeShellArg (concatStringsSep "," instance.labels)} \ 261 --config ${configFile} 262 263 # and write back the configured labels 264 echo "$LABELS_WANTED" > "$LABELS_FILE" 265 fi 266 267 '') 268 ]; 269 ExecStart = "${cfg.package}/bin/act_runner daemon --config ${configFile}"; 270 SupplementaryGroups = 271 optionals (wantsDocker) [ 272 "docker" 273 ] 274 ++ optionals (wantsPodman) [ 275 "podman" 276 ]; 277 } 278 // optionalAttrs (instance.tokenFile != null) { 279 EnvironmentFile = instance.tokenFile; 280 }; 281 }; 282 in 283 mapAttrs' mkRunnerService cfg.instances; 284 }; 285}