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