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 "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 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 };
63
64 configFile = mkOption {
65 type = types.nullOr types.path;
66 default = null;
67 description = ''
68 Specify a configuration file that Loki should use.
69 '';
70 };
71
72 extraFlags = mkOption {
73 type = types.listOf types.str;
74 default = [ ];
75 example = [ "--server.http-listen-port=3101" ];
76 description = ''
77 Specify a list of additional command line flags,
78 which get escaped and are then passed to Loki.
79 '';
80 };
81 };
82
83 config = mkIf cfg.enable {
84 assertions = [
85 {
86 assertion = (
87 (cfg.configuration == { } -> cfg.configFile != null)
88 && (cfg.configFile != null -> cfg.configuration == { })
89 );
90 message = ''
91 Please specify either
92 'services.loki.configuration' or
93 'services.loki.configFile'.
94 '';
95 }
96 ];
97
98 environment.systemPackages = [ cfg.package ]; # logcli
99
100 users.groups.${cfg.group} = { };
101 users.users.${cfg.user} = {
102 description = "Loki Service User";
103 group = cfg.group;
104 home = cfg.dataDir;
105 createHome = true;
106 isSystemUser = true;
107 };
108
109 systemd.services.loki = {
110 description = "Loki Service Daemon";
111 wants = [ "network-online.target" ];
112 after = [ "network-online.target" ];
113 wantedBy = [ "multi-user.target" ];
114
115 serviceConfig =
116 let
117 conf =
118 if cfg.configFile == null then
119 # Config validation may fail when using extraFlags = [ "-config.expand-env=true" ].
120 # To work around this, we simply skip it when extraFlags is not empty.
121 if cfg.extraFlags == [ ] then
122 validateConfig (prettyJSON cfg.configuration)
123 else
124 prettyJSON cfg.configuration
125 else
126 cfg.configFile;
127 validateConfig =
128 file:
129 pkgs.runCommand "validate-loki-conf"
130 {
131 nativeBuildInputs = [ cfg.package ];
132 }
133 ''
134 loki -verify-config -config.file "${file}"
135 ln -s "${file}" "$out"
136 '';
137 in
138 {
139 ExecStart = "${cfg.package}/bin/loki --config.file=${conf} ${escapeShellArgs cfg.extraFlags}";
140 User = cfg.user;
141 Restart = "always";
142 PrivateTmp = true;
143 ProtectHome = true;
144 ProtectSystem = "full";
145 DevicePolicy = "closed";
146 NoNewPrivileges = true;
147 WorkingDirectory = cfg.dataDir;
148 };
149 };
150 };
151}