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