1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib)
10 escapeShellArgs
11 mkEnableOption
12 mkIf
13 mkOption
14 types
15 ;
16
17 cfg = config.services.loki;
18
19 prettyJSON =
20 conf:
21 pkgs.runCommand "loki-config.json" { } ''
22 echo '${builtins.toJSON conf}' | ${pkgs.jq}/bin/jq 'del(._module)' > $out
23 '';
24
25in
26{
27 options.services.loki = {
28 enable = mkEnableOption "Grafana Loki";
29
30 user = mkOption {
31 type = types.str;
32 default = "loki";
33 description = ''
34 User under which the Loki service runs.
35 '';
36 };
37
38 package = lib.mkPackageOption pkgs "grafana-loki" { };
39
40 group = mkOption {
41 type = types.str;
42 default = "loki";
43 description = ''
44 Group under which the Loki service runs.
45 '';
46 };
47
48 dataDir = mkOption {
49 type = types.path;
50 default = "/var/lib/loki";
51 description = ''
52 Specify the data directory for Loki.
53 '';
54 };
55
56 configuration = mkOption {
57 type = (pkgs.formats.json { }).type;
58 default = { };
59 description = ''
60 Specify the configuration for Loki in Nix.
61
62 See [documentation of Grafana Loki](https://grafana.com/docs/loki/latest/configure/) for all available options.
63
64 Cannot be specified together with {option}`services.loki.configFile`.
65 '';
66 };
67
68 configFile = mkOption {
69 type = types.nullOr types.path;
70 default = null;
71 description = ''
72 Specify a configuration file that Loki should use.
73
74 Cannot be specified together with {option}`services.loki.configuration`.
75 '';
76 };
77
78 extraFlags = mkOption {
79 type = types.listOf types.str;
80 default = [ ];
81 example = [ "--server.http-listen-port=3101" ];
82 description = ''
83 Specify a list of additional command line flags,
84 which get escaped and are then passed to Loki.
85 '';
86 };
87 };
88
89 config = mkIf cfg.enable {
90 assertions = [
91 {
92 assertion = (
93 (cfg.configuration == { } -> cfg.configFile != null)
94 && (cfg.configFile != null -> cfg.configuration == { })
95 );
96 message = ''
97 Please specify either
98 'services.loki.configuration' or
99 'services.loki.configFile'.
100 '';
101 }
102 ];
103
104 environment.systemPackages = [ cfg.package ]; # logcli
105
106 users.groups.${cfg.group} = { };
107 users.users.${cfg.user} = {
108 description = "Loki Service User";
109 group = cfg.group;
110 home = cfg.dataDir;
111 createHome = true;
112 isSystemUser = true;
113 };
114
115 systemd.services.loki = {
116 description = "Loki Service Daemon";
117 wants = [ "network-online.target" ];
118 after = [ "network-online.target" ];
119 wantedBy = [ "multi-user.target" ];
120
121 serviceConfig =
122 let
123 conf =
124 if cfg.configFile == null then
125 # Config validation may fail when using extraFlags = [ "-config.expand-env=true" ].
126 # To work around this, we simply skip it when extraFlags is not empty.
127 if cfg.extraFlags == [ ] then
128 validateConfig (prettyJSON cfg.configuration)
129 else
130 prettyJSON cfg.configuration
131 else
132 cfg.configFile;
133 validateConfig =
134 file:
135 pkgs.runCommand "validate-loki-conf"
136 {
137 nativeBuildInputs = [ cfg.package ];
138 }
139 ''
140 loki -verify-config -config.file "${file}"
141 ln -s "${file}" "$out"
142 '';
143 in
144 {
145 ExecStart = "${cfg.package}/bin/loki --config.file=${conf} ${escapeShellArgs cfg.extraFlags}";
146 User = cfg.user;
147 Restart = "always";
148 PrivateTmp = true;
149 ProtectHome = true;
150 ProtectSystem = "full";
151 DevicePolicy = "closed";
152 NoNewPrivileges = true;
153 WorkingDirectory = cfg.dataDir;
154 };
155 };
156 };
157}