about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMartin Weinelt <mweinelt@users.noreply.github.com>2023-05-12 17:54:16 +0200
committerGitHub <noreply@github.com>2023-05-12 17:54:16 +0200
commit1c80c494ca347bf8e61b70c55653f7ed12f9d8cc (patch)
treefcc5f435f7027d3252c199589f3d364526b39466
parent7bbda7a7a1f850821dce44e0e7c127fbf57f5689 (diff)
parent1c963cea481fcf01e5386492149fa11eeeed7469 (diff)
Merge pull request #228422 from mweinelt/gitea-actions-runner-module
nixos/gitea-actions-runner: init
-rw-r--r--nixos/doc/manual/release-notes/rl-2305.section.md2
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/continuous-integration/gitea-actions-runner.nix237
3 files changed, 240 insertions, 0 deletions
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index 0edcbc0b8e007..e53c99f1dc6f5 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -56,6 +56,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [gemstash](https://github.com/rubygems/gemstash), a RubyGems.org cache and private gem server. Available as [services.gemstash](#opt-services.gemstash.enable).
 
+- [gitea-actions-runner](https://gitea.com/gitea/act_runner), a CI runner for Gitea/Forgejo Actions. Available as [services.gitea-actions-runner](#opt-services.gitea-actions-runner.instances).
+
 - [gmediarender](https://github.com/hzeller/gmrender-resurrect), a simple, headless UPnP/DLNA renderer.  Available as [services.gmediarender](options.html#opt-services.gmediarender.enable).
 
 - [harmonia](https://github.com/nix-community/harmonia/), Nix binary cache implemented in rust using libnix-store. Available as [services.harmonia](options.html#opt-services.harmonia.enable).
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 0b0634884c718..9ca58fdc08757 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -374,6 +374,7 @@
   ./services/continuous-integration/buildbot/master.nix
   ./services/continuous-integration/buildbot/worker.nix
   ./services/continuous-integration/buildkite-agents.nix
+  ./services/continuous-integration/gitea-actions-runner.nix
   ./services/continuous-integration/github-runner.nix
   ./services/continuous-integration/github-runners.nix
   ./services/continuous-integration/gitlab-runner.nix
diff --git a/nixos/modules/services/continuous-integration/gitea-actions-runner.nix b/nixos/modules/services/continuous-integration/gitea-actions-runner.nix
new file mode 100644
index 0000000000000..4b9046c98e8b6
--- /dev/null
+++ b/nixos/modules/services/continuous-integration/gitea-actions-runner.nix
@@ -0,0 +1,237 @@
+{ config
+, lib
+, pkgs
+, utils
+, ...
+}:
+
+let
+  inherit (lib)
+    any
+    attrValues
+    concatStringsSep
+    escapeShellArg
+    hasInfix
+    hasSuffix
+    optionalAttrs
+    optionals
+    literalExpression
+    mapAttrs'
+    mkEnableOption
+    mkOption
+    mkPackageOptionMD
+    mkIf
+    nameValuePair
+    types
+  ;
+
+  inherit (utils)
+    escapeSystemdPath
+  ;
+
+  cfg = config.services.gitea-actions-runner;
+
+  # Check whether any runner instance label requires a container runtime
+  # Empty label strings result in the upstream defined defaultLabels, which require docker
+  # https://gitea.com/gitea/act_runner/src/tag/v0.1.5/internal/app/cmd/register.go#L93-L98
+  hasDockerScheme = instance:
+    instance.labels == [] || any (label: hasInfix ":docker:" label) instance.labels;
+  wantsContainerRuntime = any hasDockerScheme (attrValues cfg.instances);
+
+  hasHostScheme = instance: any (label: hasSuffix ":host" label) instance.labels;
+
+  # provide shorthands for whether container runtimes are enabled
+  hasDocker = config.virtualisation.docker.enable;
+  hasPodman = config.virtualisation.podman.enable;
+
+  tokenXorTokenFile = instance:
+    (instance.token == null && instance.tokenFile != null) ||
+    (instance.token != null && instance.tokenFile == null);
+in
+{
+  meta.maintainers = with lib.maintainers; [
+    hexa
+  ];
+
+  options.services.gitea-actions-runner = with types; {
+    package = mkPackageOptionMD pkgs "gitea-actions-runner" { };
+
+    instances = mkOption {
+      default = {};
+      description = lib.mdDoc ''
+        Gitea Actions Runner instances.
+      '';
+      type = attrsOf (submodule {
+        options = {
+          enable = mkEnableOption (lib.mdDoc "Gitea Actions Runner instance");
+
+          name = mkOption {
+            type = str;
+            example = literalExpression "config.networking.hostName";
+            description = lib.mdDoc ''
+              The name identifying the runner instance towards the Gitea/Forgejo instance.
+            '';
+          };
+
+          url = mkOption {
+            type = str;
+            example = "https://forge.example.com";
+            description = lib.mdDoc ''
+              Base URL of your Gitea/Forgejo instance.
+            '';
+          };
+
+          token = mkOption {
+            type = nullOr str;
+            default = null;
+            description = lib.mdDoc ''
+              Plain token to register at the configured Gitea/Forgejo instance.
+            '';
+          };
+
+          tokenFile = mkOption {
+            type = nullOr (either str path);
+            default = null;
+            description = lib.mdDoc ''
+              Path to an environment file, containing the `TOKEN` environment
+              variable, that holds a token to register at the configured
+              Gitea/Forgejo instance.
+            '';
+          };
+
+          labels = mkOption {
+            type = listOf str;
+            example = literalExpression ''
+              [
+                # provide a debian base with nodejs for actions
+                "debian-latest:docker://node:18-bullseye"
+                # fake the ubuntu name, because node provides no ubuntu builds
+                "ubuntu-latest:docker://node:18-bullseye"
+                # provide native execution on the host
+                #"native:host"
+              ]
+            '';
+            description = lib.mdDoc ''
+              Labels used to map jobs to their runtime environment. Changing these
+              labels currently requires a new registration token.
+
+              Many common actions require bash, git and nodejs, as well as a filesystem
+              that follows the filesystem hierarchy standard.
+            '';
+          };
+
+          hostPackages = mkOption {
+            type = listOf package;
+            default = with pkgs; [
+              bash
+              coreutils
+              curl
+              gawk
+              gitMinimal
+              gnused
+              nodejs
+              wget
+            ];
+            defaultText = literalExpression ''
+              with pkgs; [
+                bash
+                coreutils
+                curl
+                gawk
+                gitMinimal
+                gnused
+                nodejs
+                wget
+              ]
+            '';
+            description = lib.mdDoc ''
+              List of packages, that are available to actions, when the runner is configured
+              with a host execution label.
+            '';
+          };
+        };
+      });
+    };
+  };
+
+  config = mkIf (cfg.instances != {}) {
+    assertions = [ {
+      assertion = any tokenXorTokenFile (attrValues cfg.instances);
+      message = "Instances of gitea-actions-runner can have `token` or `tokenFile`, not both.";
+    } {
+      assertion = wantsContainerRuntime -> hasDocker || hasPodman;
+      message = "Label configuration on gitea-actions-runner instance requires either docker or podman.";
+    } ];
+
+    systemd.services = let
+      mkRunnerService = name: instance: let
+        wantsContainerRuntime = hasDockerScheme instance;
+        wantsHost = hasHostScheme instance;
+        wantsDocker = wantsContainerRuntime && config.virtualisation.docker.enable;
+        wantsPodman = wantsContainerRuntime && config.virtualisation.podman.enable;
+      in
+        nameValuePair "gitea-runner-${escapeSystemdPath name}" {
+          inherit (instance) enable;
+          description = "Gitea Actions Runner";
+          after = [
+            "network-online.target"
+          ] ++ optionals (wantsDocker) [
+            "docker.service"
+          ] ++ optionals (wantsPodman) [
+            "podman.service"
+          ];
+          wantedBy = [
+            "multi-user.target"
+          ];
+          environment = optionalAttrs (instance.token != null) {
+            TOKEN = "${instance.token}";
+          } // optionalAttrs (wantsPodman) {
+            DOCKER_HOST = "unix:///run/podman/podman.sock";
+          };
+          path = with pkgs; [
+            coreutils
+          ] ++ lib.optionals wantsHost instance.hostPackages;
+          serviceConfig = {
+            DynamicUser = true;
+            User = "gitea-runner";
+            StateDirectory = "gitea-runner";
+            WorkingDirectory = "-/var/lib/gitea-runner/${name}";
+            ExecStartPre = pkgs.writeShellScript "gitea-register-runner-${name}" ''
+              export INSTANCE_DIR="$STATE_DIRECTORY/${name}"
+              mkdir -vp "$INSTANCE_DIR"
+              cd "$INSTANCE_DIR"
+
+              # force reregistration on changed labels
+              export LABELS_FILE="$INSTANCE_DIR/.labels"
+              export LABELS_WANTED="$(echo ${escapeShellArg (concatStringsSep "\n" instance.labels)} | sort)"
+              export LABELS_CURRENT="$(cat $LABELS_FILE 2>/dev/null || echo 0)"
+
+              if [ ! -e "$INSTANCE_DIR/.runner" ] || [ "$LABELS_WANTED" != "$LABELS_CURRENT" ]; then
+                # remove existing registration file, so that changing the labels forces a re-registation
+                rm -v "$INSTANCE_DIR/.runner" || true
+
+                # perform the registration
+                ${cfg.package}/bin/act_runner register --no-interactive \
+                  --instance ${escapeShellArg instance.url} \
+                  --token "$TOKEN" \
+                  --name ${escapeShellArg instance.name} \
+                  --labels ${escapeShellArg (concatStringsSep "," instance.labels)}
+
+                # and write back the configured labels
+                echo "$LABELS_WANTED" > "$LABELS_FILE"
+              fi
+
+            '';
+            ExecStart = "${cfg.package}/bin/act_runner daemon";
+            SupplementaryGroups = optionals (wantsDocker) [
+              "docker"
+            ] ++ optionals (wantsPodman) [
+              "podman"
+            ];
+          } // optionalAttrs (instance.tokenFile != null) {
+            EnvironmentFile = instance.tokenFile;
+          };
+        };
+    in mapAttrs' mkRunnerService cfg.instances;
+  };
+}