Merge pull request #228422 from mweinelt/gitea-actions-runner-module

nixos/gitea-actions-runner: init

Changed files
+240
nixos
doc
manual
release-notes
modules
services
continuous-integration
+2
nixos/doc/manual/release-notes/rl-2305.section.md
···
- [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).
+1
nixos/modules/module-list.nix
···
./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
+237
nixos/modules/services/continuous-integration/gitea-actions-runner.nix
···
+
{ 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;
+
};
+
}