33 cfg = config.services.gitea-actions-runner;
35 settingsFormat = pkgs.formats.yaml { };
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
41 instance: instance.labels == [ ] || any (label: hasInfix ":docker:" label) instance.labels;
42 wantsContainerRuntime = any hasDockerScheme (attrValues cfg.instances);
44 hasHostScheme = instance: any (label: hasSuffix ":host" label) instance.labels;
46 # provide shorthands for whether container runtimes are enabled
47 hasDocker = config.virtualisation.docker.enable;
48 hasPodman = config.virtualisation.podman.enable;
52 (instance.token == null && instance.tokenFile != null)
53 || (instance.token != null && instance.tokenFile == null);
56 meta.maintainers = with lib.maintainers; [
60 options.services.gitea-actions-runner = with types; {
61 package = mkPackageOption pkgs "gitea-actions-runner" { };
63 instances = mkOption {
66 Gitea Actions Runner instances.
68 type = attrsOf (submodule {
70 enable = mkEnableOption "Gitea Actions Runner instance";
74 example = literalExpression "config.networking.hostName";
76 The name identifying the runner instance towards the Gitea/Forgejo instance.
82 example = "https://forge.example.com";
84 Base URL of your Gitea/Forgejo instance.
92 Plain token to register at the configured Gitea/Forgejo instance.
96 tokenFile = mkOption {
97 type = nullOr (either str path);
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.
108 example = literalExpression ''
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
119 Labels used to map jobs to their runtime environment. Changing these
120 labels currently requires a new registration token.
122 Many common actions require bash, git and nodejs, as well as a filesystem
123 that follows the filesystem hierarchy standard.
126 settings = mkOption {
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
132 type = types.submodule {
133 freeformType = settingsFormat.type;
139 hostPackages = mkOption {
140 type = listOf package;
141 default = with pkgs; [
151 defaultText = literalExpression ''
164 List of packages, that are available to actions, when the runner is configured
165 with a host execution label.
173 config = mkIf (cfg.instances != { }) {
176 assertion = any tokenXorTokenFile (attrValues cfg.instances);
177 message = "Instances of gitea-actions-runner can have `token` or `tokenFile`, not both.";
180 assertion = wantsContainerRuntime -> hasDocker || hasPodman;
181 message = "Label configuration on gitea-actions-runner instance requires either docker or podman.";
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;
196 nameValuePair "gitea-runner-${escapeSystemdPath name}" {
197 inherit (instance) enable;
198 description = "Gitea Actions Runner";
199 wants = [ "network-online.target" ];
202 "network-online.target"
204 ++ optionals (wantsDocker) [
207 ++ optionals (wantsPodman) [
214 optionalAttrs (instance.token != null) {
215 TOKEN = "${instance.token}";
217 // optionalAttrs (wantsPodman) {
218 DOCKER_HOST = "unix:///run/podman/podman.sock";
221 HOME = "/var/lib/gitea-runner/${name}";
228 ++ lib.optionals wantsHost instance.hostPackages;
232 User = "gitea-runner";
233 StateDirectory = "gitea-runner";
234 WorkingDirectory = "-/var/lib/gitea-runner/${name}";
236 # gitea-runner might fail when gitea is restarted during upgrade.
237 Restart = "on-failure";
241 (pkgs.writeShellScript "gitea-register-runner-${name}" ''
242 export INSTANCE_DIR="$STATE_DIRECTORY/${name}"
243 mkdir -vp "$INSTANCE_DIR"
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)"
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
255 # perform the registration
256 ${cfg.package}/bin/act_runner register --no-interactive \
257 --instance ${escapeShellArg instance.url} \
259 --name ${escapeShellArg instance.name} \
260 --labels ${escapeShellArg (concatStringsSep "," instance.labels)} \
261 --config ${configFile}
263 # and write back the configured labels
264 echo "$LABELS_WANTED" > "$LABELS_FILE"
269 ExecStart = "${cfg.package}/bin/act_runner daemon --config ${configFile}";
270 SupplementaryGroups =
271 optionals (wantsDocker) [
274 ++ optionals (wantsPodman) [
278 // optionalAttrs (instance.tokenFile != null) {
279 EnvironmentFile = instance.tokenFile;
283 mapAttrs' mkRunnerService cfg.instances;