1{ pkgs, config, lib, ... }:
2
3let
4 cfg = config.services.cockpit;
5 inherit (lib) types mkEnableOption mkOption mkIf mdDoc literalMD mkPackageOptionMD;
6 settingsFormat = pkgs.formats.ini {};
7in {
8 options = {
9 services.cockpit = {
10 enable = mkEnableOption (mdDoc "Cockpit");
11
12 package = mkPackageOptionMD pkgs "Cockpit" {
13 default = [ "cockpit" ];
14 };
15
16 settings = lib.mkOption {
17 type = settingsFormat.type;
18
19 default = {};
20
21 description = mdDoc ''
22 Settings for cockpit that will be saved in /etc/cockpit/cockpit.conf.
23
24 See the [documentation](https://cockpit-project.org/guide/latest/cockpit.conf.5.html), that is also available with `man cockpit.conf.5` for details.
25 '';
26 };
27
28 port = mkOption {
29 description = mdDoc "Port where cockpit will listen.";
30 type = types.port;
31 default = 9090;
32 };
33
34 openFirewall = mkOption {
35 description = mdDoc "Open port for cockpit.";
36 type = types.bool;
37 default = false;
38 };
39 };
40 };
41 config = mkIf cfg.enable {
42
43 # expose cockpit-bridge system-wide
44 environment.systemPackages = [ cfg.package ];
45
46 # allow cockpit to find its plugins
47 environment.pathsToLink = [ "/share/cockpit" ];
48
49 # generate cockpit settings
50 environment.etc."cockpit/cockpit.conf".source = settingsFormat.generate "cockpit.conf" cfg.settings;
51
52 security.pam.services.cockpit = {};
53
54 networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
55
56 # units are in reverse sort order if you ls $out/lib/systemd/system
57 # all these units are basically verbatim translated from upstream
58
59 # Translation from $out/lib/systemd/system/systemd-cockpithttps.slice
60 systemd.slices.system-cockpithttps = {
61 description = "Resource limits for all cockpit-ws-https@.service instances";
62 sliceConfig = {
63 TasksMax = 200;
64 MemoryHigh = "75%";
65 MemoryMax = "90%";
66 };
67 };
68
69 # Translation from $out/lib/systemd/system/cockpit-wsinstance-https@.socket
70 systemd.sockets."cockpit-wsinstance-https@" = {
71 unitConfig = {
72 Description = "Socket for Cockpit Web Service https instance %I";
73 BindsTo = [ "cockpit.service" "cockpit-wsinstance-https@%i.service" ];
74 # clean up the socket after the service exits, to prevent fd leak
75 # this also effectively prevents a DoS by starting arbitrarily many sockets, as
76 # the services are resource-limited by system-cockpithttps.slice
77 Documentation = "man:cockpit-ws(8)";
78 };
79 socketConfig = {
80 ListenStream = "/run/cockpit/wsinstance/https@%i.sock";
81 SocketUser = "root";
82 SocketMode = "0600";
83 };
84 };
85
86 # Translation from $out/lib/systemd/system/cockpit-wsinstance-https@.service
87 systemd.services."cockpit-wsinstance-https@" = {
88 description = "Cockpit Web Service https instance %I";
89 bindsTo = [ "cockpit.service"];
90 path = [ cfg.package ];
91 documentation = [ "man:cockpit-ws(8)" ];
92 serviceConfig = {
93 Slice = "system-cockpithttps.slice";
94 ExecStart = "${cfg.package}/libexec/cockpit-ws --for-tls-proxy --port=0";
95 User = "root";
96 Group = "";
97 };
98 };
99
100 # Translation from $out/lib/systemd/system/cockpit-wsinstance-http.socket
101 systemd.sockets.cockpit-wsinstance-http = {
102 unitConfig = {
103 Description = "Socket for Cockpit Web Service http instance";
104 BindsTo = "cockpit.service";
105 Documentation = "man:cockpit-ws(8)";
106 };
107 socketConfig = {
108 ListenStream = "/run/cockpit/wsinstance/http.sock";
109 SocketUser = "root";
110 SocketMode = "0600";
111 };
112 };
113
114 # Translation from $out/lib/systemd/system/cockpit-wsinstance-https-factory.socket
115 systemd.sockets.cockpit-wsinstance-https-factory = {
116 unitConfig = {
117 Description = "Socket for Cockpit Web Service https instance factory";
118 BindsTo = "cockpit.service";
119 Documentation = "man:cockpit-ws(8)";
120 };
121 socketConfig = {
122 ListenStream = "/run/cockpit/wsinstance/https-factory.sock";
123 Accept = true;
124 SocketUser = "root";
125 SocketMode = "0600";
126 };
127 };
128
129 # Translation from $out/lib/systemd/system/cockpit-wsinstance-https-factory@.service
130 systemd.services."cockpit-wsinstance-https-factory@" = {
131 description = "Cockpit Web Service https instance factory";
132 documentation = [ "man:cockpit-ws(8)" ];
133 path = [ cfg.package ];
134 serviceConfig = {
135 ExecStart = "${cfg.package}/libexec/cockpit-wsinstance-factory";
136 User = "root";
137 };
138 };
139
140 # Translation from $out/lib/systemd/system/cockpit-wsinstance-http.service
141 systemd.services."cockpit-wsinstance-http" = {
142 description = "Cockpit Web Service http instance";
143 bindsTo = [ "cockpit.service" ];
144 path = [ cfg.package ];
145 documentation = [ "man:cockpit-ws(8)" ];
146 serviceConfig = {
147 ExecStart = "${cfg.package}/libexec/cockpit-ws --no-tls --port=0";
148 User = "root";
149 Group = "";
150 };
151 };
152
153 # Translation from $out/lib/systemd/system/cockpit.socket
154 systemd.sockets."cockpit" = {
155 unitConfig = {
156 Description = "Cockpit Web Service Socket";
157 Documentation = "man:cockpit-ws(8)";
158 Wants = "cockpit-motd.service";
159 };
160 socketConfig = {
161 ListenStream = cfg.port;
162 ExecStartPost = [
163 "-${cfg.package}/share/cockpit/motd/update-motd \"\" localhost"
164 "-${pkgs.coreutils}/bin/ln -snf active.motd /run/cockpit/motd"
165 ];
166 ExecStopPost = "-${pkgs.coreutils}/bin/ln -snf inactive.motd /run/cockpit/motd";
167 };
168 wantedBy = [ "sockets.target" ];
169 };
170
171 # Translation from $out/lib/systemd/system/cockpit.service
172 systemd.services."cockpit" = {
173 description = "Cockpit Web Service";
174 documentation = [ "man:cockpit-ws(8)" ];
175 restartIfChanged = true;
176 path = with pkgs; [ coreutils cfg.package ];
177 requires = [ "cockpit.socket" "cockpit-wsinstance-http.socket" "cockpit-wsinstance-https-factory.socket" ];
178 after = [ "cockpit-wsinstance-http.socket" "cockpit-wsinstance-https-factory.socket" ];
179 environment = {
180 G_MESSAGES_DEBUG = "cockpit-ws,cockpit-bridge";
181 };
182 serviceConfig = {
183 RuntimeDirectory="cockpit/tls";
184 ExecStartPre = [
185 # cockpit-tls runs in a more constrained environment, these + means that these commands
186 # will run with full privilege instead of inside that constrained environment
187 # See https://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart= for details
188 "+${cfg.package}/libexec/cockpit-certificate-ensure --for-cockpit-tls"
189 ];
190 ExecStart = "${cfg.package}/libexec/cockpit-tls";
191 User = "root";
192 Group = "";
193 NoNewPrivileges = true;
194 ProtectSystem = "strict";
195 ProtectHome = true;
196 PrivateTmp = true;
197 PrivateDevices = true;
198 ProtectKernelTunables = true;
199 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
200 MemoryDenyWriteExecute = true;
201 };
202 };
203
204 # Translation from $out/lib/systemd/system/cockpit-motd.service
205 # This part basically implements a motd state machine:
206 # - If cockpit.socket is enabled then /run/cockpit/motd points to /run/cockpit/active.motd
207 # - If cockpit.socket is disabled then /run/cockpit/motd points to /run/cockpit/inactive.motd
208 # - As cockpit.socket is disabled by default, /run/cockpit/motd points to /run/cockpit/inactive.motd
209 # /run/cockpit/active.motd is generated dynamically by cockpit-motd.service
210 systemd.services."cockpit-motd" = {
211 path = with pkgs; [ nettools ];
212 serviceConfig = {
213 Type = "oneshot";
214 ExecStart = "${cfg.package}/share/cockpit/motd/update-motd";
215 };
216 description = "Cockpit motd updater service";
217 documentation = [ "man:cockpit-ws(8)" ];
218 wants = [ "network.target" ];
219 after = [ "network.target" "cockpit.socket" ];
220 };
221
222 systemd.tmpfiles.rules = [ # From $out/lib/tmpfiles.d/cockpit-tmpfiles.conf
223 "C /run/cockpit/inactive.motd 0640 root root - ${cfg.package}/share/cockpit/motd/inactive.motd"
224 "f /run/cockpit/active.motd 0640 root root -"
225 "L+ /run/cockpit/motd - - - - inactive.motd"
226 "d /etc/cockpit/ws-certs.d 0600 root root 0"
227 ];
228 };
229
230 meta.maintainers = pkgs.cockpit.meta.maintainers;
231}