1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.traefik;
7 jsonValue = with types;
8 let
9 valueType = nullOr (oneOf [
10 bool
11 int
12 float
13 str
14 (lazyAttrsOf valueType)
15 (listOf valueType)
16 ]) // {
17 description = "JSON value";
18 emptyValue.value = { };
19 };
20 in valueType;
21 dynamicConfigFile = if cfg.dynamicConfigFile == null then
22 pkgs.runCommand "config.toml" {
23 buildInputs = [ pkgs.remarshal ];
24 preferLocalBuild = true;
25 } ''
26 remarshal -if json -of toml \
27 < ${
28 pkgs.writeText "dynamic_config.json"
29 (builtins.toJSON cfg.dynamicConfigOptions)
30 } \
31 > $out
32 ''
33 else
34 cfg.dynamicConfigFile;
35 staticConfigFile = if cfg.staticConfigFile == null then
36 pkgs.runCommand "config.toml" {
37 buildInputs = [ pkgs.yj ];
38 preferLocalBuild = true;
39 } ''
40 yj -jt -i \
41 < ${
42 pkgs.writeText "static_config.json" (builtins.toJSON
43 (recursiveUpdate cfg.staticConfigOptions {
44 providers.file.filename = "${dynamicConfigFile}";
45 }))
46 } \
47 > $out
48 ''
49 else
50 cfg.staticConfigFile;
51in {
52 options.services.traefik = {
53 enable = mkEnableOption (lib.mdDoc "Traefik web server");
54
55 staticConfigFile = mkOption {
56 default = null;
57 example = literalExpression "/path/to/static_config.toml";
58 type = types.nullOr types.path;
59 description = lib.mdDoc ''
60 Path to traefik's static configuration to use.
61 (Using that option has precedence over `staticConfigOptions` and `dynamicConfigOptions`)
62 '';
63 };
64
65 staticConfigOptions = mkOption {
66 description = lib.mdDoc ''
67 Static configuration for Traefik.
68 '';
69 type = jsonValue;
70 default = { entryPoints.http.address = ":80"; };
71 example = {
72 entryPoints.web.address = ":8080";
73 entryPoints.http.address = ":80";
74
75 api = { };
76 };
77 };
78
79 dynamicConfigFile = mkOption {
80 default = null;
81 example = literalExpression "/path/to/dynamic_config.toml";
82 type = types.nullOr types.path;
83 description = lib.mdDoc ''
84 Path to traefik's dynamic configuration to use.
85 (Using that option has precedence over `dynamicConfigOptions`)
86 '';
87 };
88
89 dynamicConfigOptions = mkOption {
90 description = lib.mdDoc ''
91 Dynamic configuration for Traefik.
92 '';
93 type = jsonValue;
94 default = { };
95 example = {
96 http.routers.router1 = {
97 rule = "Host(`localhost`)";
98 service = "service1";
99 };
100
101 http.services.service1.loadBalancer.servers =
102 [{ url = "http://localhost:8080"; }];
103 };
104 };
105
106 dataDir = mkOption {
107 default = "/var/lib/traefik";
108 type = types.path;
109 description = lib.mdDoc ''
110 Location for any persistent data traefik creates, ie. acme
111 '';
112 };
113
114 group = mkOption {
115 default = "traefik";
116 type = types.str;
117 example = "docker";
118 description = lib.mdDoc ''
119 Set the group that traefik runs under.
120 For the docker backend this needs to be set to `docker` instead.
121 '';
122 };
123
124 package = mkOption {
125 default = pkgs.traefik;
126 defaultText = literalExpression "pkgs.traefik";
127 type = types.package;
128 description = lib.mdDoc "Traefik package to use.";
129 };
130 };
131
132 config = mkIf cfg.enable {
133 systemd.tmpfiles.rules = [ "d '${cfg.dataDir}' 0700 traefik traefik - -" ];
134
135 systemd.services.traefik = {
136 description = "Traefik web server";
137 after = [ "network-online.target" ];
138 wantedBy = [ "multi-user.target" ];
139 startLimitIntervalSec = 86400;
140 startLimitBurst = 5;
141 serviceConfig = {
142 ExecStart =
143 "${cfg.package}/bin/traefik --configfile=${staticConfigFile}";
144 Type = "simple";
145 User = "traefik";
146 Group = cfg.group;
147 Restart = "on-failure";
148 AmbientCapabilities = "cap_net_bind_service";
149 CapabilityBoundingSet = "cap_net_bind_service";
150 NoNewPrivileges = true;
151 LimitNPROC = 64;
152 LimitNOFILE = 1048576;
153 PrivateTmp = true;
154 PrivateDevices = true;
155 ProtectHome = true;
156 ProtectSystem = "full";
157 ReadWriteDirectories = cfg.dataDir;
158 };
159 };
160
161 users.users.traefik = {
162 group = "traefik";
163 home = cfg.dataDir;
164 createHome = true;
165 isSystemUser = true;
166 };
167
168 users.groups.traefik = { };
169 };
170}