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