1{ config
2, lib
3, pkgs
4, ...
5}:
6
7let
8 cfg = config.services.woodpecker-agents;
9
10 agentModule = lib.types.submodule {
11 options = {
12 enable = lib.mkEnableOption (lib.mdDoc "this Woodpecker-Agent. Agents execute tasks generated by a Server, every install will need one server and at least one agent");
13
14 package = lib.mkPackageOptionMD pkgs "woodpecker-agent" { };
15
16 environment = lib.mkOption {
17 default = { };
18 type = lib.types.attrsOf lib.types.str;
19 example = lib.literalExpression ''
20 {
21 WOODPECKER_SERVER = "localhost:9000";
22 WOODPECKER_BACKEND = "docker";
23 DOCKER_HOST = "unix:///run/podman/podman.sock";
24 }
25 '';
26 description = lib.mdDoc "woodpecker-agent config environment variables, for other options read the [documentation](https://woodpecker-ci.org/docs/administration/agent-config)";
27 };
28
29 extraGroups = lib.mkOption {
30 type = lib.types.listOf lib.types.str;
31 default = [ ];
32 example = [ "podman" ];
33 description = lib.mdDoc ''
34 Additional groups for the systemd service.
35 '';
36 };
37
38 environmentFile = lib.mkOption {
39 type = lib.types.listOf lib.types.path;
40 default = [ ];
41 example = [ "/var/secrets/woodpecker-agent.env" ];
42 description = lib.mdDoc ''
43 File to load environment variables
44 from. This is helpful for specifying secrets.
45 Example content of environmentFile:
46 ```
47 WOODPECKER_AGENT_SECRET=your-shared-secret-goes-here
48 ```
49 '';
50 };
51 };
52 };
53
54 mkAgentService = name: agentCfg: {
55 name = "woodpecker-agent-${name}";
56 value = {
57 description = "Woodpecker-Agent Service - ${name}";
58 wantedBy = [ "multi-user.target" ];
59 after = [ "network-online.target" ];
60 wants = [ "network-online.target" ];
61 serviceConfig = {
62 DynamicUser = true;
63 SupplementaryGroups = agentCfg.extraGroups;
64 EnvironmentFile = agentCfg.environmentFile;
65 ExecStart = lib.getExe agentCfg.package;
66 Restart = "on-failure";
67 RestartSec = 15;
68 CapabilityBoundingSet = "";
69 NoNewPrivileges = true;
70 ProtectSystem = "strict";
71 PrivateTmp = true;
72 PrivateDevices = true;
73 PrivateUsers = true;
74 ProtectHostname = true;
75 ProtectClock = true;
76 ProtectKernelTunables = true;
77 ProtectKernelModules = true;
78 ProtectKernelLogs = true;
79 ProtectControlGroups = true;
80 RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
81 LockPersonality = true;
82 MemoryDenyWriteExecute = true;
83 RestrictRealtime = true;
84 RestrictSUIDSGID = true;
85 PrivateMounts = true;
86 SystemCallArchitectures = "native";
87 SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
88 BindReadOnlyPaths = [
89 "-/etc/resolv.conf"
90 "-/etc/nsswitch.conf"
91 "-/etc/ssl/certs"
92 "-/etc/static/ssl/certs"
93 "-/etc/hosts"
94 "-/etc/localtime"
95 ];
96 };
97 inherit (agentCfg) environment;
98 };
99 };
100in
101{
102 meta.maintainers = with lib.maintainers; [ janik ambroisie ];
103
104 options = {
105 services.woodpecker-agents = {
106 agents = lib.mkOption {
107 default = { };
108 type = lib.types.attrsOf agentModule;
109 example = {
110 docker = {
111 environment = {
112 WOODPECKER_SERVER = "localhost:9000";
113 WOODPECKER_BACKEND = "docker";
114 DOCKER_HOST = "unix:///run/podman/podman.sock";
115 };
116
117 extraGroups = [ "docker" ];
118
119 environmentFile = "/run/secrets/woodpecker/agent-secret.txt";
120 };
121
122 exec = {
123 environment = {
124 WOODPECKER_SERVER = "localhost:9000";
125 WOODPECKER_BACKEND = "exec";
126 };
127
128 environmentFile = "/run/secrets/woodpecker/agent-secret.txt";
129 };
130 };
131 description = lib.mdDoc "woodpecker-agents configurations";
132 };
133 };
134 };
135
136 config = {
137 systemd.services =
138 let
139 mkServices = lib.mapAttrs' mkAgentService;
140 enabledAgents = lib.filterAttrs (_: agent: agent.enable) cfg.agents;
141 in
142 mkServices enabledAgents;
143 };
144}