32 cfg = config.services.gitea-actions-runner;
34 settingsFormat = pkgs.formats.yaml { };
36 # Check whether any runner instance label requires a container runtime
37 # Empty label strings result in the upstream defined defaultLabels, which require docker
38 # https://gitea.com/gitea/act_runner/src/tag/v0.1.5/internal/app/cmd/register.go#L93-L98
39 hasDockerScheme = instance:
40 instance.labels == [] || any (label: hasInfix ":docker:" label) instance.labels;
41 wantsContainerRuntime = any hasDockerScheme (attrValues cfg.instances);
43 hasHostScheme = instance: any (label: hasSuffix ":host" label) instance.labels;
45 # provide shorthands for whether container runtimes are enabled
46 hasDocker = config.virtualisation.docker.enable;
47 hasPodman = config.virtualisation.podman.enable;
49 tokenXorTokenFile = instance:
50 (instance.token == null && instance.tokenFile != null) ||
51 (instance.token != null && instance.tokenFile == null);
54 meta.maintainers = with lib.maintainers; [
58 options.services.gitea-actions-runner = with types; {
59 package = mkPackageOption pkgs "gitea-actions-runner" { };
61 instances = mkOption {
64 Gitea Actions Runner instances.
66 type = attrsOf (submodule {
68 enable = mkEnableOption "Gitea Actions Runner instance";
72 example = literalExpression "config.networking.hostName";
74 The name identifying the runner instance towards the Gitea/Forgejo instance.
80 example = "https://forge.example.com";
82 Base URL of your Gitea/Forgejo instance.
90 Plain token to register at the configured Gitea/Forgejo instance.
94 tokenFile = mkOption {
95 type = nullOr (either str path);
98 Path to an environment file, containing the `TOKEN` environment
99 variable, that holds a token to register at the configured
100 Gitea/Forgejo instance.
106 example = literalExpression ''
108 # provide a debian base with nodejs for actions
109 "debian-latest:docker://node:18-bullseye"
110 # fake the ubuntu name, because node provides no ubuntu builds
111 "ubuntu-latest:docker://node:18-bullseye"
112 # provide native execution on the host
117 Labels used to map jobs to their runtime environment. Changing these
118 labels currently requires a new registration token.
120 Many common actions require bash, git and nodejs, as well as a filesystem
121 that follows the filesystem hierarchy standard.
124 settings = mkOption {
126 Configuration for `act_runner daemon`.
127 See https://gitea.com/gitea/act_runner/src/branch/main/internal/pkg/config/config.example.yaml for an example configuration
130 type = types.submodule {
131 freeformType = settingsFormat.type;
137 hostPackages = mkOption {
138 type = listOf package;
139 default = with pkgs; [
149 defaultText = literalExpression ''
162 List of packages, that are available to actions, when the runner is configured
163 with a host execution label.
171 config = mkIf (cfg.instances != {}) {
173 assertion = any tokenXorTokenFile (attrValues cfg.instances);
174 message = "Instances of gitea-actions-runner can have `token` or `tokenFile`, not both.";
176 assertion = wantsContainerRuntime -> hasDocker || hasPodman;
177 message = "Label configuration on gitea-actions-runner instance requires either docker or podman.";
180 systemd.services = let
181 mkRunnerService = name: instance: let
182 wantsContainerRuntime = hasDockerScheme instance;
183 wantsHost = hasHostScheme instance;
184 wantsDocker = wantsContainerRuntime && config.virtualisation.docker.enable;
185 wantsPodman = wantsContainerRuntime && config.virtualisation.podman.enable;
186 configFile = settingsFormat.generate "config.yaml" instance.settings;
188 nameValuePair "gitea-runner-${escapeSystemdPath name}" {
189 inherit (instance) enable;
190 description = "Gitea Actions Runner";
191 wants = [ "network-online.target" ];
193 "network-online.target"
194 ] ++ optionals (wantsDocker) [
196 ] ++ optionals (wantsPodman) [
202 environment = optionalAttrs (instance.token != null) {
203 TOKEN = "${instance.token}";
204 } // optionalAttrs (wantsPodman) {
205 DOCKER_HOST = "unix:///run/podman/podman.sock";
207 HOME = "/var/lib/gitea-runner/${name}";
211 ] ++ lib.optionals wantsHost instance.hostPackages;
214 User = "gitea-runner";
215 StateDirectory = "gitea-runner";
216 WorkingDirectory = "-/var/lib/gitea-runner/${name}";
218 # gitea-runner might fail when gitea is restarted during upgrade.
219 Restart = "on-failure";
222 ExecStartPre = [(pkgs.writeShellScript "gitea-register-runner-${name}" ''
223 export INSTANCE_DIR="$STATE_DIRECTORY/${name}"
224 mkdir -vp "$INSTANCE_DIR"
227 # force reregistration on changed labels
228 export LABELS_FILE="$INSTANCE_DIR/.labels"
229 export LABELS_WANTED="$(echo ${escapeShellArg (concatStringsSep "\n" instance.labels)} | sort)"
230 export LABELS_CURRENT="$(cat $LABELS_FILE 2>/dev/null || echo 0)"
232 if [ ! -e "$INSTANCE_DIR/.runner" ] || [ "$LABELS_WANTED" != "$LABELS_CURRENT" ]; then
233 # remove existing registration file, so that changing the labels forces a re-registration
234 rm -v "$INSTANCE_DIR/.runner" || true
236 # perform the registration
237 ${cfg.package}/bin/act_runner register --no-interactive \
238 --instance ${escapeShellArg instance.url} \
240 --name ${escapeShellArg instance.name} \
241 --labels ${escapeShellArg (concatStringsSep "," instance.labels)} \
242 --config ${configFile}
244 # and write back the configured labels
245 echo "$LABELS_WANTED" > "$LABELS_FILE"
249 ExecStart = "${cfg.package}/bin/act_runner daemon --config ${configFile}";
250 SupplementaryGroups = optionals (wantsDocker) [
252 ] ++ optionals (wantsPodman) [
255 } // optionalAttrs (instance.tokenFile != null) {
256 EnvironmentFile = instance.tokenFile;
259 in mapAttrs' mkRunnerService cfg.instances;