{ config , lib , pkgs , ... }: let cfg = config.services.woodpecker-agents; agentModule = lib.types.submodule { options = { enable = lib.mkEnableOption "this Woodpecker-Agent. Agents execute tasks generated by a Server, every install will need one server and at least one agent"; package = lib.mkPackageOption pkgs "woodpecker-agent" { }; environment = lib.mkOption { default = { }; type = lib.types.attrsOf lib.types.str; example = lib.literalExpression '' { WOODPECKER_SERVER = "localhost:9000"; WOODPECKER_BACKEND = "docker"; DOCKER_HOST = "unix:///run/podman/podman.sock"; } ''; description = "woodpecker-agent config environment variables, for other options read the [documentation](https://woodpecker-ci.org/docs/administration/agent-config)"; }; extraGroups = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; example = [ "podman" ]; description = '' Additional groups for the systemd service. ''; }; path = lib.mkOption { type = lib.types.listOf lib.types.package; default = [ ]; example = [ "" ]; description = '' Additional packages that should be added to the agent's `PATH`. Mostly useful for the `local` backend. ''; }; environmentFile = lib.mkOption { type = lib.types.listOf lib.types.path; default = [ ]; example = [ "/var/secrets/woodpecker-agent.env" ]; description = '' File to load environment variables from. This is helpful for specifying secrets. Example content of environmentFile: ``` WOODPECKER_AGENT_SECRET=your-shared-secret-goes-here ``` ''; }; }; }; mkAgentService = name: agentCfg: { name = "woodpecker-agent-${name}"; value = { description = "Woodpecker-Agent Service - ${name}"; wantedBy = [ "multi-user.target" ]; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; serviceConfig = { DynamicUser = true; SupplementaryGroups = agentCfg.extraGroups; EnvironmentFile = agentCfg.environmentFile; ExecStart = lib.getExe agentCfg.package; Restart = "on-failure"; RestartSec = 15; CapabilityBoundingSet = ""; NoNewPrivileges = true; ProtectSystem = "strict"; PrivateTmp = true; PrivateDevices = true; PrivateUsers = true; ProtectHostname = true; ProtectClock = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectKernelLogs = true; ProtectControlGroups = true; RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ]; LockPersonality = true; MemoryDenyWriteExecute = true; RestrictRealtime = true; RestrictSUIDSGID = true; PrivateMounts = true; SystemCallArchitectures = "native"; SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap"; BindReadOnlyPaths = [ "-/etc/resolv.conf" "-/etc/nsswitch.conf" "-/etc/ssl/certs" "-/etc/static/ssl/certs" "-/etc/hosts" "-/etc/localtime" ]; }; inherit (agentCfg) environment path; }; }; in { meta.maintainers = with lib.maintainers; [ ambroisie ]; options = { services.woodpecker-agents = { agents = lib.mkOption { default = { }; type = lib.types.attrsOf agentModule; example = lib.literalExpression '' { podman = { environment = { WOODPECKER_SERVER = "localhost:9000"; WOODPECKER_BACKEND = "docker"; DOCKER_HOST = "unix:///run/podman/podman.sock"; }; extraGroups = [ "podman" ]; environmentFile = [ "/run/secrets/woodpecker/agent-secret.txt" ]; }; exec = { environment = { WOODPECKER_SERVER = "localhost:9000"; WOODPECKER_BACKEND = "local"; }; environmentFile = [ "/run/secrets/woodpecker/agent-secret.txt" ]; path = [ # Needed to clone repos git git-lfs woodpecker-plugin-git # Used by the runner as the default shell bash # Most likely to be used in pipeline definitions coreutils ]; }; } ''; description = "woodpecker-agents configurations"; }; }; }; config = { systemd.services = let mkServices = lib.mapAttrs' mkAgentService; enabledAgents = lib.filterAttrs (_: agent: agent.enable) cfg.agents; in mkServices enabledAgents; }; }