1
0
Fork 0
mirror of https://github.com/LnL7/nix-darwin.git synced 2024-12-14 11:57:34 +00:00
nix-darwin/modules/services/gitlab-runner.nix

644 lines
24 KiB
Nix
Raw Normal View History

2021-02-09 08:45:34 +00:00
{ config, lib, pkgs, ... }:
with builtins;
with lib;
let
cfg = config.services.gitlab-runner;
hasDocker = config.virtualisation.docker.enable;
hashedServices = mapAttrs'
(name: service: nameValuePair
"${name}_${config.networking.hostName}_${
substring 0 12
(hashString "md5" (unsafeDiscardStringContext (toJSON service)))}"
service)
cfg.services;
configPath = "$HOME/.gitlab-runner/config.toml";
configureScript = pkgs.writeShellScriptBin "gitlab-runner-configure" (
if (cfg.configFile != null) then ''
mkdir -p $(dirname ${configPath})
cp ${cfg.configFile} ${configPath}
# make config file readable by service
chown -R --reference=$HOME $(dirname ${configPath})
'' else ''
export CONFIG_FILE=${configPath}
mkdir -p $(dirname ${configPath})
# remove no longer existing services
gitlab-runner verify --delete
# current and desired state
NEEDED_SERVICES=$(echo ${concatStringsSep " " (attrNames hashedServices)} | tr " " "\n")
REGISTERED_SERVICES=$(gitlab-runner list 2>&1 | grep 'Executor' | awk '{ print $1 }')
# difference between current and desired state
NEW_SERVICES=$(grep -vxF -f <(echo "$REGISTERED_SERVICES") <(echo "$NEEDED_SERVICES") || true)
OLD_SERVICES=$(grep -vxF -f <(echo "$NEEDED_SERVICES") <(echo "$REGISTERED_SERVICES") || true)
# register new services
${concatStringsSep "\n" (mapAttrsToList (name: service: ''
if echo "$NEW_SERVICES" | grep -xq ${name}; then
bash -c ${escapeShellArg (concatStringsSep " \\\n " ([
"set -a && source ${service.registrationConfigFile} &&"
"gitlab-runner register"
"--non-interactive"
"--name ${name}"
"--executor ${service.executor}"
"--limit ${toString service.limit}"
"--request-concurrency ${toString service.requestConcurrency}"
"--maximum-timeout ${toString service.maximumTimeout}"
] ++ service.registrationFlags
++ optional (service.buildsDir != null)
"--builds-dir ${service.buildsDir}"
++ optional (service.cloneUrl != null)
"--clone-url ${service.cloneUrl}"
++ optional (service.preCloneScript != null)
"--pre-clone-script ${service.preCloneScript}"
++ optional (service.preBuildScript != null)
"--pre-build-script ${service.preBuildScript}"
++ optional (service.postBuildScript != null)
"--post-build-script ${service.postBuildScript}"
++ optional (service.tagList != [ ])
"--tag-list ${concatStringsSep "," service.tagList}"
++ optional service.runUntagged
"--run-untagged"
++ optional service.protected
"--access-level ref_protected"
++ optional service.debugTraceDisabled
"--debug-trace-disabled"
++ map (e: "--env ${escapeShellArg e}") (mapAttrsToList (name: value: "${name}=${value}") service.environmentVariables)
++ optionals (hasPrefix "docker" service.executor) (
assert (
assertMsg (service.dockerImage != null)
"dockerImage option is required for ${service.executor} executor (${name})");
[ "--docker-image ${service.dockerImage}" ]
++ optional service.dockerDisableCache
"--docker-disable-cache"
++ optional service.dockerPrivileged
"--docker-privileged"
++ map (v: "--docker-volumes ${escapeShellArg v}") service.dockerVolumes
++ map (v: "--docker-extra-hosts ${escapeShellArg v}") service.dockerExtraHosts
++ map (v: "--docker-allowed-images ${escapeShellArg v}") service.dockerAllowedImages
++ map (v: "--docker-allowed-services ${escapeShellArg v}") service.dockerAllowedServices
)
))} && sleep 1 || exit 1
fi
'') hashedServices)}
# unregister old services
for NAME in $(echo "$OLD_SERVICES")
do
[ ! -z "$NAME" ] && gitlab-runner unregister \
--name "$NAME" && sleep 1
done
# update global options
remarshal --if toml --of json ${configPath} \
| jq -cM ${escapeShellArg (concatStringsSep " | " [
".check_interval = ${toJSON cfg.checkInterval}"
".concurrent = ${toJSON cfg.concurrent}"
".sentry_dsn = ${toJSON cfg.sentryDSN}"
".listen_address = ${toJSON cfg.prometheusListenAddress}"
".session_server.listen_address = ${toJSON cfg.sessionServer.listenAddress}"
".session_server.advertise_address = ${toJSON cfg.sessionServer.advertiseAddress}"
".session_server.session_timeout = ${toJSON cfg.sessionServer.sessionTimeout}"
"del(.[] | nulls)"
"del(.session_server[] | nulls)"
])} \
| remarshal --if json --of toml \
| sponge ${configPath}
# make config file readable by service
chown -R --reference=$HOME $(dirname ${configPath})
'');
startScript = pkgs.writeShellScriptBin "gitlab-runner-start" ''
export CONFIG_FILE=${configPath}
exec gitlab-runner run --working-directory $HOME
'';
in
{
options.services.gitlab-runner = {
2024-04-14 21:02:32 +00:00
enable = mkEnableOption "Gitlab Runner";
2021-02-09 08:45:34 +00:00
configFile = mkOption {
type = types.nullOr types.path;
default = null;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Configuration file for gitlab-runner.
{option}`configFile` takes precedence over {option}`services`.
{option}`checkInterval` and {option}`concurrent` will be ignored too.
2021-02-09 08:45:34 +00:00
This option is deprecated, please use {option}`services` instead.
You can use {option}`registrationConfigFile` and
{option}`registrationFlags`
2021-02-09 08:45:34 +00:00
for settings not covered by this module.
'';
};
checkInterval = mkOption {
type = types.int;
default = 0;
example = literalExpression "with lib; (length (attrNames config.services.gitlab-runner.services)) * 3";
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Defines the interval length, in seconds, between new jobs check.
The default value is 3;
if set to 0 or lower, the default value will be used.
See [runner documentation](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#how-check_interval-works) for more information.
2021-02-09 08:45:34 +00:00
'';
};
concurrent = mkOption {
type = types.int;
default = 1;
example = literalExpression "config.nix.maxJobs";
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Limits how many jobs globally can be run concurrently.
The most upper limit of jobs using all defined runners.
0 does not mean unlimited.
'';
};
sentryDSN = mkOption {
type = types.nullOr types.str;
default = null;
example = "https://public:private@host:port/1";
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Data Source Name for tracking of all system level errors to Sentry.
'';
};
prometheusListenAddress = mkOption {
type = types.nullOr types.str;
default = null;
example = "localhost:8080";
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Address (&lt;host&gt;:&lt;port&gt;) on which the Prometheus metrics HTTP server
should be listening.
'';
};
sessionServer = mkOption {
type = types.submodule {
options = {
listenAddress = mkOption {
type = types.nullOr types.str;
default = null;
example = "0.0.0.0:8093";
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
An internal URL to be used for the session server.
'';
};
advertiseAddress = mkOption {
type = types.nullOr types.str;
default = null;
example = "runner-host-name.tld:8093";
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
The URL that the Runner will expose to GitLab to be used
to access the session server.
Fallbacks to {option}`listenAddress` if not defined.
2021-02-09 08:45:34 +00:00
'';
};
sessionTimeout = mkOption {
type = types.int;
default = 1800;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
How long in seconds the session can stay active after
the job completes (which will block the job from finishing).
'';
};
};
};
default = { };
example = literalExpression ''
2021-02-09 08:45:34 +00:00
{
listenAddress = "0.0.0.0:8093";
}
'';
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
The session server allows the user to interact with jobs
that the Runner is responsible for. A good example of this is the
[interactive web terminal](https://docs.gitlab.com/ee/ci/interactive_web_terminal/index.html).
2021-02-09 08:45:34 +00:00
'';
};
gracefulTermination = mkOption {
type = types.bool;
default = false;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Finish all remaining jobs before stopping.
If not set gitlab-runner will stop immediatly without waiting
for jobs to finish, which will lead to failed builds.
'';
};
gracefulTimeout = mkOption {
type = types.str;
default = "infinity";
example = "5min 20s";
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Time to wait until a graceful shutdown is turned into a forceful one.
'';
};
package = mkOption {
type = types.package;
default = pkgs.gitlab-runner;
defaultText = "pkgs.gitlab-runner";
example = literalExpression "pkgs.gitlab-runner_1_11";
2024-04-14 21:02:32 +00:00
description = "Gitlab Runner package to use.";
2021-02-09 08:45:34 +00:00
};
extraPackages = mkOption {
type = types.listOf types.package;
default = [ ];
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Extra packages to add to PATH for the gitlab-runner process.
'';
};
services = mkOption {
2024-04-14 21:02:32 +00:00
description = "GitLab Runner services.";
2021-02-09 08:45:34 +00:00
default = { };
example = literalExpression ''
2021-02-09 08:45:34 +00:00
{
# runner for building in docker via host's nix-daemon
# nix store will be readable in runner, might be insecure
nix = {
# File should contain at least these two variables:
# `CI_SERVER_URL`
# `REGISTRATION_TOKEN`
registrationConfigFile = "/run/secrets/gitlab-runner-registration";
dockerImage = "alpine";
dockerVolumes = [
"/nix/store:/nix/store:ro"
"/nix/var/nix/db:/nix/var/nix/db:ro"
"/nix/var/nix/daemon-socket:/nix/var/nix/daemon-socket:ro"
];
dockerDisableCache = true;
preBuildScript = pkgs.writeScript "setup-container" '''
mkdir -p -m 0755 /nix/var/log/nix/drvs
mkdir -p -m 0755 /nix/var/nix/gcroots
mkdir -p -m 0755 /nix/var/nix/profiles
mkdir -p -m 0755 /nix/var/nix/temproots
mkdir -p -m 0755 /nix/var/nix/userpool
mkdir -p -m 1777 /nix/var/nix/gcroots/per-user
mkdir -p -m 1777 /nix/var/nix/profiles/per-user
mkdir -p -m 0755 /nix/var/nix/profiles/per-user/root
mkdir -p -m 0700 "$HOME/.nix-defexpr"
. ''${pkgs.nix}/etc/profile.d/nix.sh
''${pkgs.nix}/bin/nix-env -i ''${concatStringsSep " " (with pkgs; [ nix cacert git openssh ])}
''${pkgs.nix}/bin/nix-channel --add https://nixos.org/channels/nixpkgs-unstable
''${pkgs.nix}/bin/nix-channel --update nixpkgs
''';
environmentVariables = {
ENV = "/etc/profile";
USER = "root";
NIX_REMOTE = "daemon";
PATH = "/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/bin:/sbin:/usr/bin:/usr/sbin";
NIX_SSL_CERT_FILE = "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt";
};
tagList = [ "nix" ];
};
# runner for building docker images
docker-images = {
# File should contain at least these two variables:
# `CI_SERVER_URL`
# `REGISTRATION_TOKEN`
registrationConfigFile = "/run/secrets/gitlab-runner-registration";
dockerImage = "docker:stable";
dockerVolumes = [
"/var/run/docker.sock:/var/run/docker.sock"
];
tagList = [ "docker-images" ];
};
# runner for executing stuff on host system (very insecure!)
# make sure to add required packages (including git!)
# to `environment.systemPackages`
shell = {
# File should contain at least these two variables:
# `CI_SERVER_URL`
# `REGISTRATION_TOKEN`
registrationConfigFile = "/run/secrets/gitlab-runner-registration";
executor = "shell";
tagList = [ "shell" ];
};
# runner for everything else
default = {
# File should contain at least these two variables:
# `CI_SERVER_URL`
# `REGISTRATION_TOKEN`
registrationConfigFile = "/run/secrets/gitlab-runner-registration";
dockerImage = "debian:stable";
};
}
'';
type = types.attrsOf (types.submodule {
options = {
registrationConfigFile = mkOption {
type = types.path;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Absolute path to a file with environment variables
used for gitlab-runner registration.
A list of all supported environment variables can be found in
`gitlab-runner register --help`.
2021-02-09 08:45:34 +00:00
Ones that you probably want to set is
`CI_SERVER_URL=<CI server URL>`
2021-02-09 08:45:34 +00:00
`REGISTRATION_TOKEN=<registration secret>`
2021-02-09 08:45:34 +00:00
'';
};
registrationFlags = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "--docker-helper-image my/gitlab-runner-helper" ];
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Extra command-line flags passed to
`gitlab-runner register`.
Execute `gitlab-runner register --help`
2021-02-09 08:45:34 +00:00
for a list of supported flags.
'';
};
environmentVariables = mkOption {
type = types.attrsOf types.str;
default = { };
example = { NAME = "value"; };
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Custom environment variables injected to build environment.
For secrets you can use {option}`registrationConfigFile`
with `RUNNER_ENV` variable set.
2021-02-09 08:45:34 +00:00
'';
};
executor = mkOption {
type = types.str;
default = "docker";
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Select executor, eg. shell, docker, etc.
See [runner documentation](https://docs.gitlab.com/runner/executors/README.html) for more information.
2021-02-09 08:45:34 +00:00
'';
};
buildsDir = mkOption {
type = types.nullOr types.path;
default = null;
example = "/var/lib/gitlab-runner/builds";
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Absolute path to a directory where builds will be stored
in context of selected executor (Locally, Docker, SSH).
'';
};
cloneUrl = mkOption {
type = types.nullOr types.str;
default = null;
example = "http://gitlab.example.local";
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Overwrite the URL for the GitLab instance. Used if the Runner cant connect to GitLab on the URL GitLab exposes itself.
'';
};
dockerImage = mkOption {
type = types.nullOr types.str;
default = null;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Docker image to be used.
'';
};
dockerVolumes = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "/var/run/docker.sock:/var/run/docker.sock" ];
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Bind-mount a volume and create it
if it doesn't exist prior to mounting.
'';
};
dockerDisableCache = mkOption {
type = types.bool;
default = false;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Disable all container caching.
'';
};
dockerPrivileged = mkOption {
type = types.bool;
default = false;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Give extended privileges to container.
'';
};
dockerExtraHosts = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "other-host:127.0.0.1" ];
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Add a custom host-to-IP mapping.
'';
};
dockerAllowedImages = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "ruby:*" "python:*" "php:*" "my.registry.tld:5000/*:*" ];
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Whitelist allowed images.
'';
};
dockerAllowedServices = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "postgres:9" "redis:*" "mysql:*" ];
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Whitelist allowed services.
'';
};
preCloneScript = mkOption {
type = types.nullOr types.path;
default = null;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Runner-specific command script executed before code is pulled.
'';
};
preBuildScript = mkOption {
type = types.nullOr types.path;
default = null;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Runner-specific command script executed after code is pulled,
just before build executes.
'';
};
postBuildScript = mkOption {
type = types.nullOr types.path;
default = null;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Runner-specific command script executed after code is pulled
and just after build executes.
'';
};
tagList = mkOption {
type = types.listOf types.str;
default = [ ];
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Tag list.
'';
};
runUntagged = mkOption {
type = types.bool;
default = false;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Register to run untagged builds; defaults to
`true` when {option}`tagList` is empty.
2021-02-09 08:45:34 +00:00
'';
};
limit = mkOption {
type = types.int;
default = 0;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Limit how many jobs can be handled concurrently by this service.
0 (default) simply means don't limit.
'';
};
requestConcurrency = mkOption {
type = types.int;
default = 0;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
Limit number of concurrent requests for new jobs from GitLab.
'';
};
maximumTimeout = mkOption {
type = types.int;
default = 0;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
What is the maximum timeout (in seconds) that will be set for
job when using this Runner. 0 (default) simply means don't limit.
'';
};
protected = mkOption {
type = types.bool;
default = false;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
When set to true Runner will only run on pipelines
triggered on protected branches.
'';
};
debugTraceDisabled = mkOption {
type = types.bool;
default = false;
2024-04-14 21:02:32 +00:00
description = ''
2021-02-09 08:45:34 +00:00
When set to true Runner will disable the possibility of
using the `CI_DEBUG_TRACE` feature.
2021-02-09 08:45:34 +00:00
'';
};
};
});
};
};
config = mkIf cfg.enable {
users.users.gitlab-runner =
{ name = "gitlab-runner";
2022-01-04 08:13:03 +00:00
uid = mkDefault 532;
2021-02-19 02:38:09 +00:00
# gid = mkDefault config.users.groups.gitlab-runner.gid;
2021-02-09 08:45:34 +00:00
home = mkDefault "/var/lib/gitlab-runner";
shell = "/bin/bash";
description = "Gitlab agent user";
};
users.groups.gitlab-runner =
2022-01-04 08:14:21 +00:00
{ name = "gitlab-runner";
2022-01-04 08:13:03 +00:00
gid = mkDefault 532;
2021-02-09 08:45:34 +00:00
description = "Gitlab agent user group";
};
2021-02-19 02:38:09 +00:00
# system.activationScripts.preActivation.text = let user = config.users.users.gitlab-runner; in ''
# mkdir -p '${user.home}'
# chown ${toString user.uid}:${toString user.gid} '${user.home}'
#'';
2021-02-09 08:45:34 +00:00
warnings = optional (cfg.configFile != null) "services.gitlab-runner.`configFile` is deprecated, please use services.gitlab-runner.`services`.";
environment.systemPackages = [ cfg.package ];
launchd.daemons.gitlab-runner = {
environment = { #config.networking.proxy.envVars // {
HOME = "${config.users.users.gitlab-runner.home}";
NIX_SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
} // (if config.nix.useDaemon then { NIX_REMOTE = "daemon"; } else {});
path = with pkgs; [
bash
gawk
jq
moreutils
remarshal
2022-01-04 08:13:03 +00:00
# util-linux
2021-02-09 08:45:34 +00:00
cfg.package
coreutils
gnugrep
gnused
] ++ cfg.extraPackages;
script = ''
${configureScript}/bin/gitlab-runner-configure && ${startScript}/bin/gitlab-runner-start
'';
serviceConfig = {
ProcessType = "Interactive";
ThrottleInterval = 30;
2021-02-19 02:38:09 +00:00
# StandardOutPath = "/var/lib/gitlab-runner/out.log";
# StandardErrorPath = "/var/lib/gitlab-runner/err.log";
2021-02-09 08:45:34 +00:00
# The combination of KeepAlive.NetworkState and WatchPaths
# will ensure that buildkite-agent is started on boot, but
# after networking is available (so the hostname is
# correct).
RunAtLoad = true;
# KeepAlive.NetworkState = true;
WatchPaths = [
"/etc/resolv.conf"
"/Library/Preferences/SystemConfiguration/NetworkInterfaces.plist"
];
GroupName = "gitlab-runner";
UserName = "gitlab-runner";
WorkingDirectory = config.users.users.gitlab-runner.home;
};
};
# systemd.services.gitlab-runner = {
# description = "Gitlab Runner";
# documentation = [ "https://docs.gitlab.com/runner/" ];
# after = [ "network.target" ]
# ++ optional hasDocker "docker.service";
# requires = optional hasDocker "docker.service";
# wantedBy = [ "multi-user.target" ];
# environment = config.networking.proxy.envVars // {
# HOME = "/var/lib/gitlab-runner";
# };
# path = with pkgs; [
# bash
# gawk
# jq
# moreutils
# remarshal
# util-linux
# cfg.package
# ] ++ cfg.extraPackages;
# reloadIfChanged = true;
# serviceConfig = {
# # Set `DynamicUser` under `systemd.services.gitlab-runner.serviceConfig`
# # to `lib.mkForce false` in your configuration to run this service as root.
# # You can also set `User` and `Group` options to run this service as desired user.
# # Make sure to restart service or changes won't apply.
# DynamicUser = true;
# StateDirectory = "gitlab-runner";
# SupplementaryGroups = optional hasDocker "docker";
# ExecStartPre = "!${configureScript}/bin/gitlab-runner-configure";
# ExecStart = "${startScript}/bin/gitlab-runner-start";
# ExecReload = "!${configureScript}/bin/gitlab-runner-configure";
# } // optionalAttrs (cfg.gracefulTermination) {
# TimeoutStopSec = "${cfg.gracefulTimeout}";
# KillSignal = "SIGQUIT";
# KillMode = "process";
# };
# };
# # Enable docker if `docker` executor is used in any service
# virtualisation.docker.enable = mkIf (
# any (s: s.executor == "docker") (attrValues cfg.services)
# ) (mkDefault true);
};
imports = [
(mkRenamedOptionModule [ "services" "gitlab-runner" "packages" ] [ "services" "gitlab-runner" "extraPackages" ] )
(mkRemovedOptionModule [ "services" "gitlab-runner" "configOptions" ] "Use services.gitlab-runner.services option instead" )
(mkRemovedOptionModule [ "services" "gitlab-runner" "workDir" ] "You should move contents of workDir (if any) to /var/lib/gitlab-runner" )
];
}