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;
51
52 finalStaticConfigFile =
53 if cfg.environmentFiles == []
54 then staticConfigFile
55 else "/run/traefik/config.toml";
56in {
57 options.services.traefik = {
58 enable = mkEnableOption (lib.mdDoc "Traefik web server");
59
60 staticConfigFile = mkOption {
61 default = null;
62 example = literalExpression "/path/to/static_config.toml";
63 type = types.nullOr types.path;
64 description = lib.mdDoc ''
65 Path to traefik's static configuration to use.
66 (Using that option has precedence over `staticConfigOptions` and `dynamicConfigOptions`)
67 '';
68 };
69
70 staticConfigOptions = mkOption {
71 description = lib.mdDoc ''
72 Static configuration for Traefik.
73 '';
74 type = jsonValue;
75 default = { entryPoints.http.address = ":80"; };
76 example = {
77 entryPoints.web.address = ":8080";
78 entryPoints.http.address = ":80";
79
80 api = { };
81 };
82 };
83
84 dynamicConfigFile = mkOption {
85 default = null;
86 example = literalExpression "/path/to/dynamic_config.toml";
87 type = types.nullOr types.path;
88 description = lib.mdDoc ''
89 Path to traefik's dynamic configuration to use.
90 (Using that option has precedence over `dynamicConfigOptions`)
91 '';
92 };
93
94 dynamicConfigOptions = mkOption {
95 description = lib.mdDoc ''
96 Dynamic configuration for Traefik.
97 '';
98 type = jsonValue;
99 default = { };
100 example = {
101 http.routers.router1 = {
102 rule = "Host(`localhost`)";
103 service = "service1";
104 };
105
106 http.services.service1.loadBalancer.servers =
107 [{ url = "http://localhost:8080"; }];
108 };
109 };
110
111 dataDir = mkOption {
112 default = "/var/lib/traefik";
113 type = types.path;
114 description = lib.mdDoc ''
115 Location for any persistent data traefik creates, ie. acme
116 '';
117 };
118
119 group = mkOption {
120 default = "traefik";
121 type = types.str;
122 example = "docker";
123 description = lib.mdDoc ''
124 Set the group that traefik runs under.
125 For the docker backend this needs to be set to `docker` instead.
126 '';
127 };
128
129 package = mkOption {
130 default = pkgs.traefik;
131 defaultText = literalExpression "pkgs.traefik";
132 type = types.package;
133 description = lib.mdDoc "Traefik package to use.";
134 };
135
136 environmentFiles = mkOption {
137 default = [];
138 type = types.listOf types.path;
139 example = [ "/run/secrets/traefik.env" ];
140 description = lib.mdDoc ''
141 Files to load as environment file. Environment variables from this file
142 will be substituted into the static configuration file using envsubst.
143 '';
144 };
145 };
146
147 config = mkIf cfg.enable {
148 systemd.tmpfiles.rules = [ "d '${cfg.dataDir}' 0700 traefik traefik - -" ];
149
150 systemd.services.traefik = {
151 description = "Traefik web server";
152 after = [ "network-online.target" ];
153 wantedBy = [ "multi-user.target" ];
154 startLimitIntervalSec = 86400;
155 startLimitBurst = 5;
156 serviceConfig = {
157 EnvironmentFile = cfg.environmentFiles;
158 ExecStartPre = lib.optional (cfg.environmentFiles != [])
159 (pkgs.writeShellScript "pre-start" ''
160 umask 077
161 ${pkgs.envsubst}/bin/envsubst -i "${staticConfigFile}" > "${finalStaticConfigFile}"
162 '');
163 ExecStart = "${cfg.package}/bin/traefik --configfile=${finalStaticConfigFile}";
164 Type = "simple";
165 User = "traefik";
166 Group = cfg.group;
167 Restart = "on-failure";
168 AmbientCapabilities = "cap_net_bind_service";
169 CapabilityBoundingSet = "cap_net_bind_service";
170 NoNewPrivileges = true;
171 LimitNPROC = 64;
172 LimitNOFILE = 1048576;
173 PrivateTmp = true;
174 PrivateDevices = true;
175 ProtectHome = true;
176 ProtectSystem = "full";
177 ReadWriteDirectories = cfg.dataDir;
178 RuntimeDirectory = "traefik";
179 };
180 };
181
182 users.users.traefik = {
183 group = "traefik";
184 home = cfg.dataDir;
185 createHome = true;
186 isSystemUser = true;
187 };
188
189 users.groups.traefik = { };
190 };
191}