1{ config, lib, pkgs, ... }:
2
3let
4 cfg = config.services.haproxy;
5
6 haproxyCfg = pkgs.writeText "haproxy.conf" ''
7 global
8 # needed for hot-reload to work without dropping packets in multi-worker mode
9 stats socket /run/haproxy/haproxy.sock mode 600 expose-fd listeners level user
10
11 ${cfg.config}
12 '';
13
14in
15with lib;
16{
17 options = {
18 services.haproxy = {
19
20 enable = mkOption {
21 type = types.bool;
22 default = false;
23 description = lib.mdDoc ''
24 Whether to enable HAProxy, the reliable, high performance TCP/HTTP
25 load balancer.
26 '';
27 };
28
29 user = mkOption {
30 type = types.str;
31 default = "haproxy";
32 description = lib.mdDoc "User account under which haproxy runs.";
33 };
34
35 group = mkOption {
36 type = types.str;
37 default = "haproxy";
38 description = lib.mdDoc "Group account under which haproxy runs.";
39 };
40
41 config = mkOption {
42 type = types.nullOr types.lines;
43 default = null;
44 description = lib.mdDoc ''
45 Contents of the HAProxy configuration file,
46 {file}`haproxy.conf`.
47 '';
48 };
49 };
50 };
51
52 config = mkIf cfg.enable {
53
54 assertions = [{
55 assertion = cfg.config != null;
56 message = "You must provide services.haproxy.config.";
57 }];
58
59 # configuration file indirection is needed to support reloading
60 environment.etc."haproxy.cfg".source = haproxyCfg;
61
62 systemd.services.haproxy = {
63 description = "HAProxy";
64 after = [ "network.target" ];
65 wantedBy = [ "multi-user.target" ];
66 serviceConfig = {
67 User = cfg.user;
68 Group = cfg.group;
69 Type = "notify";
70 ExecStartPre = [
71 # when the master process receives USR2, it reloads itself using exec(argv[0]),
72 # so we create a symlink there and update it before reloading
73 "${pkgs.coreutils}/bin/ln -sf ${pkgs.haproxy}/sbin/haproxy /run/haproxy/haproxy"
74 # when running the config test, don't be quiet so we can see what goes wrong
75 "/run/haproxy/haproxy -c -f ${haproxyCfg}"
76 ];
77 ExecStart = "/run/haproxy/haproxy -Ws -f /etc/haproxy.cfg -p /run/haproxy/haproxy.pid";
78 # support reloading
79 ExecReload = [
80 "${pkgs.haproxy}/sbin/haproxy -c -f ${haproxyCfg}"
81 "${pkgs.coreutils}/bin/ln -sf ${pkgs.haproxy}/sbin/haproxy /run/haproxy/haproxy"
82 "${pkgs.coreutils}/bin/kill -USR2 $MAINPID"
83 ];
84 KillMode = "mixed";
85 SuccessExitStatus = "143";
86 Restart = "always";
87 RuntimeDirectory = "haproxy";
88 # upstream hardening options
89 NoNewPrivileges = true;
90 ProtectHome = true;
91 ProtectSystem = "strict";
92 ProtectKernelTunables = true;
93 ProtectKernelModules = true;
94 ProtectControlGroups = true;
95 SystemCallFilter= "~@cpu-emulation @keyring @module @obsolete @raw-io @reboot @swap @sync";
96 # needed in case we bind to port < 1024
97 AmbientCapabilities = "CAP_NET_BIND_SERVICE";
98 };
99 };
100
101 users.users = optionalAttrs (cfg.user == "haproxy") {
102 haproxy = {
103 group = cfg.group;
104 isSystemUser = true;
105 };
106 };
107
108 users.groups = optionalAttrs (cfg.group == "haproxy") {
109 haproxy = {};
110 };
111 };
112}