-
-
Notifications
You must be signed in to change notification settings - Fork 18.3k
nixos/{act,gitea-actions}-runner: refactor, nixos/forgejo-runner: init #485596
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b7fbc38
59721fe
3f792e4
717e843
8d7d648
9bddcc9
9672c25
07794ea
0c4559a
fb21b87
3ddee1d
5d3e1c8
5b59e17
16fd1c0
bca1eec
e35bfdc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| imports = [ | ||
| (import ./generic.nix { | ||
| attributeName = "forgejo-runner"; | ||
| name = "forgejo"; | ||
| prettyName = "Forgejo"; | ||
| runnerPrettyName = "Forgejo Runner"; | ||
| srcUrl = "https://code.forgejo.org/forgejo/runner"; | ||
| docsUrl = "https://forgejo.org/docs/latest/user/actions/overview"; | ||
| labelsUrl = "https://forgejo.org/docs/latest/admin/actions"; | ||
| mainProgram = "forgejo-runner"; | ||
| }) | ||
| ]; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,315 @@ | ||
| { | ||
| attributeName, | ||
| docsUrl, | ||
| labelsUrl, | ||
| mainProgram, | ||
| name, | ||
| prettyName, | ||
| runnerPrettyName, | ||
| srcUrl, | ||
| }: | ||
| { | ||
| config, | ||
| lib, | ||
| pkgs, | ||
| utils, | ||
| ... | ||
| }: | ||
|
|
||
| let | ||
| inherit (lib) | ||
| any | ||
| attrValues | ||
| concatStringsSep | ||
| escapeShellArg | ||
| getExe | ||
| hasInfix | ||
| hasSuffix | ||
| literalExpression | ||
| maintainers | ||
| mapAttrs' | ||
| mkEnableOption | ||
| mkIf | ||
| mkMerge | ||
| mkOption | ||
| mkPackageOption | ||
| nameValuePair | ||
| optionalAttrs | ||
| optionals | ||
| types | ||
| ; | ||
|
|
||
| inherit (types) | ||
| attrsOf | ||
| either | ||
| listOf | ||
| nullOr | ||
| package | ||
| path | ||
| str | ||
| submodule | ||
| ; | ||
|
|
||
| inherit (utils) | ||
| escapeSystemdPath | ||
| ; | ||
|
|
||
| cfg = config.services.${attributeName}; | ||
|
|
||
| settingsFormat = pkgs.formats.yaml { }; | ||
| in | ||
| { | ||
| meta.maintainers = with maintainers; [ | ||
| hexa | ||
| sigmasquadron | ||
| ]; | ||
|
|
||
| options.services.${attributeName} = { | ||
| package = mkPackageOption pkgs attributeName { }; | ||
|
|
||
| instances = mkOption { | ||
| default = { }; | ||
| description = '' | ||
| ${runnerPrettyName} instances. | ||
|
|
||
| See <${docsUrl}> for additional information on how to use these CI instances. | ||
| ''; | ||
| type = attrsOf (submodule { | ||
| options = { | ||
| enable = mkEnableOption "${runnerPrettyName} instance"; | ||
|
|
||
| name = mkOption { | ||
| type = str; | ||
| example = literalExpression "config.networking.hostName"; | ||
| description = '' | ||
| The name identifying the runner instance towards the ${prettyName} instance. | ||
| ''; | ||
| }; | ||
|
|
||
| url = mkOption { | ||
| type = str; | ||
| example = "https://forge.example.com"; | ||
| description = '' | ||
| Base URL of your ${prettyName} instance. | ||
| ''; | ||
| }; | ||
|
|
||
| token = mkOption { | ||
| type = nullOr str; | ||
| default = null; | ||
| description = '' | ||
| Plain token to register at the configured ${prettyName} instance. | ||
| ''; | ||
| }; | ||
|
|
||
| tokenFile = mkOption { | ||
| type = nullOr (either str path); | ||
| default = null; | ||
| description = '' | ||
| Path to an environment file, containing the `TOKEN` environment | ||
| variable, that holds a token to register at the configured | ||
| ${prettyName} 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 = '' | ||
| 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. | ||
|
|
||
| See <${labelsUrl}> for more information. | ||
| ''; | ||
| }; | ||
| settings = mkOption { | ||
| description = '' | ||
| Configuration for the `${mainProgram}` daemon. | ||
| See <${srcUrl}/src/branch/main/internal/pkg/config/config.example.yaml> for an example configuration. | ||
| ''; | ||
|
|
||
| type = types.submodule { | ||
| freeformType = settingsFormat.type; | ||
| }; | ||
|
|
||
| default = { }; | ||
| }; | ||
|
|
||
| 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 = '' | ||
| List of packages that are available to actions when the runner | ||
| is configured with a host execution label. | ||
| ''; | ||
| }; | ||
| }; | ||
| }); | ||
| }; | ||
| }; | ||
|
|
||
| config = mkMerge [ | ||
| (mkIf (cfg.instances != { }) ( | ||
| let | ||
| tokenXorTokenFile = | ||
| instance: | ||
| (instance.token == null && instance.tokenFile != null) | ||
| || (instance.token != null && instance.tokenFile == null); | ||
|
|
||
| # Check whether any runner instance label requires a container runtime. | ||
| # Empty label strings result in the upstream defined defaultLabels, which require docker. | ||
| hasDockerScheme = | ||
| instance: instance.labels == [ ] || any (label: hasInfix ":docker:" label) instance.labels; | ||
| anyWantsContainerRuntime = any hasDockerScheme (attrValues cfg.instances); | ||
|
|
||
| # provide shorthands for whether container runtimes are enabled and whether host execution is possible. | ||
| hasDocker = config.virtualisation.docker.enable; | ||
| hasPodman = config.virtualisation.podman.enable; | ||
| hasHostScheme = instance: any (label: hasSuffix ":host" label) instance.labels; | ||
| in | ||
| { | ||
| assertions = [ | ||
| { | ||
| assertion = any tokenXorTokenFile (attrValues cfg.instances); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unrelated to your changes, but there's a bug here. This only throws, if all the configs are incorrect (in other words it only does NOT throw when there is at least one correct config), it should throw if it finds at least one incorrect config. Example: this config builds when it should throw instead, because the existing "test" instance is correct, it ignored "test2" being incorrect with both diff --git a/nixos/tests/forgejo.nix b/nixos/tests/forgejo.nix
index 85221a33c9ee..98d9928bd39d 100644
--- a/nixos/tests/forgejo.nix
+++ b/nixos/tests/forgejo.nix
@@ -72,6 +72,17 @@ let
];
tokenFile = "/var/lib/forgejo/runner_token";
};
+ configuration.services.forgejo-runner.instances."test2" = {
+ enable = true;
+ name = "ci";
+ url = "http://localhost:3000";
+ labels = [
+ # type ":host" does not depend on docker/podman/lxc
+ "native:host"
+ ];
+ tokenFile = "/var/lib/forgejo/runner_token";
+ token = "fake-token";
+ };
};
specialisation.dump = {
inheritParentConfig = true; |
||
| message = "${runnerPrettyName} instances may have either a `token` or a `tokenFile` configured, but not both simultaneously."; | ||
| } | ||
| { | ||
| assertion = anyWantsContainerRuntime -> hasDocker || hasPodman; | ||
| message = "At least one of the configured ${runnerPrettyName} instances require a container hypervisor, but neither Docker nor Podman are enabled."; | ||
| } | ||
| ]; | ||
|
|
||
| systemd.services = | ||
| let | ||
| mkRunnerService = | ||
| id: instance: | ||
| let | ||
| wantsContainerRuntime = hasDockerScheme instance; | ||
| wantsHost = hasHostScheme instance; | ||
| wantsDocker = wantsContainerRuntime && hasDocker; | ||
| wantsPodman = wantsContainerRuntime && hasPodman; | ||
| configFile = settingsFormat.generate "config.yaml" instance.settings; | ||
| in | ||
| nameValuePair "${name}-runner-${escapeSystemdPath id}" { | ||
| inherit (instance) enable; | ||
| description = runnerPrettyName; | ||
| wants = [ "network-online.target" ]; | ||
| 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"; | ||
| } | ||
| // { | ||
| HOME = "/var/lib/${name}-runner/${id}"; | ||
| }; | ||
| path = | ||
| with pkgs; | ||
| [ | ||
| coreutils | ||
| ] | ||
| ++ optionals wantsHost instance.hostPackages; | ||
| serviceConfig = { | ||
| DynamicUser = true; | ||
| User = "${name}-runner"; | ||
| StateDirectory = "${name}-runner"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When existing forgejo-runner users switch to the new module, the state directory will be changed. I'm not sure if the data loss is significant, I assume it can be recreated without issues and with automatic runner re-registrations. This is my current data: [root@raspi5-doboz:/var/lib/gitea-runner/raspi5-doboz]# l
total 20K
drwxr-xr-x 3 nobody nogroup 4.0K Dec 31 02:06 .
drwxr-xr-x 5 nobody nogroup 4.0K Dec 30 18:20 ..
drwxr-xr-x 4 nobody nogroup 4.0K Dec 31 01:28 .cache
-rw-r--r-- 1 nobody nogroup 64 Dec 31 02:06 .labels
-rw-r--r-- 1 nobody nogroup 478 Dec 31 02:06 .runner
[root@raspi5-doboz:/var/lib/gitea-runner/raspi5-doboz]# cat .labels
ubuntu-24.04-arm:docker://ghcr.io/catthehacker/ubuntu:act-24.04
[root@raspi5-doboz:/var/lib/gitea-runner/raspi5-doboz]# cat .runner
{
"WARNING": "This file is automatically generated by forgejo-runner. Do not edit it manually unless you know what you are doing. Removing this file will cause act runner to re-register as a new runner.",
"id": 8,
"uuid": "d2000000-0000-0000-0000-000000000000",
"name": "raspi5-doboz",
"token": "b200000000000000000000000000000000000000",
"address": "https://git.tchfoo.com",
"labels": [
"ubuntu-24.04-arm:docker://ghcr.io/catthehacker/ubuntu:act-24.04"
]
}
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the most impactful change will be losing the cache directory, since those can be large and are also used extensively in many folks' CI setups. So it will both duplicate the directory and will make folks' CI pipelines take longer, at least temporarily. Therefore I would not want this PR backported, along with the concerns that emily mentioned below. |
||
| WorkingDirectory = "-/var/lib/${name}-runner/${id}"; | ||
|
|
||
| # act_runner might fail when the forge is restarted during an upgrade. | ||
| Restart = "on-failure"; | ||
| RestartSec = 2; | ||
|
|
||
| ExecStartPre = [ | ||
| (pkgs.writeShellScript "${name}-register-runner-${id}" '' | ||
| export INSTANCE_DIR="$STATE_DIRECTORY/${id}" | ||
| 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-registration | ||
| rm -v "$INSTANCE_DIR/.runner" || true | ||
|
|
||
| # perform the registration | ||
| ${getExe cfg.package} register --no-interactive \ | ||
| --instance ${escapeShellArg instance.url} \ | ||
| --token "$TOKEN" \ | ||
| --name ${escapeShellArg instance.name} \ | ||
| --labels ${escapeShellArg (concatStringsSep "," instance.labels)} \ | ||
| --config ${configFile} | ||
|
|
||
| # and write back the configured labels | ||
| echo "$LABELS_WANTED" > "$LABELS_FILE" | ||
| fi | ||
|
|
||
| '') | ||
| ]; | ||
| ExecStart = "${getExe cfg.package} daemon --config ${configFile}"; | ||
| SupplementaryGroups = | ||
| optionals wantsDocker [ | ||
| "docker" | ||
| ] | ||
| ++ optionals wantsPodman [ | ||
| "podman" | ||
| ]; | ||
| } | ||
| // optionalAttrs (instance.tokenFile != null) { | ||
| EnvironmentFile = instance.tokenFile; | ||
| }; | ||
| }; | ||
| in | ||
| mapAttrs' mkRunnerService cfg.instances; | ||
| } | ||
| )) | ||
| ]; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| imports = [ | ||
| (import ./generic.nix { | ||
| attributeName = "gitea-actions-runner"; | ||
| docsUrl = "https://docs.gitea.com/usage/actions"; | ||
| labelsUrl = "https://docs.gitea.com/usage/actions/act-runner#labels"; | ||
| mainProgram = "act_runner"; | ||
| name = "gitea"; | ||
| prettyName = "Gitea"; | ||
| runnerPrettyName = "Gitea Actions Runner"; | ||
| srcUrl = "https://gitea.com/gitea/act_runner"; | ||
| }) | ||
| ]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: preserve the same order of arguments as the gitea runner. This order seems more logical so I think the other should be changed.