1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.lokinet;
10 dataDir = "/var/lib/lokinet";
11 settingsFormat = pkgs.formats.ini { listsAsDuplicateKeys = true; };
12 configFile = settingsFormat.generate "lokinet.ini" (
13 lib.filterAttrsRecursive (n: v: v != null) cfg.settings
14 );
15in
16with lib;
17{
18 options.services.lokinet = {
19 enable = mkEnableOption "Lokinet daemon";
20
21 package = mkPackageOption pkgs "lokinet" { };
22
23 useLocally = mkOption {
24 type = types.bool;
25 default = false;
26 example = true;
27 description = "Whether to use Lokinet locally.";
28 };
29
30 settings = mkOption {
31 type =
32 with types;
33 submodule {
34 freeformType = settingsFormat.type;
35
36 options = {
37 dns = {
38 bind = mkOption {
39 type = str;
40 default = "127.3.2.1";
41 description = "Address to bind to for handling DNS requests.";
42 };
43
44 upstream = mkOption {
45 type = listOf str;
46 default = [ "9.9.9.10" ];
47 example = [
48 "1.1.1.1"
49 "8.8.8.8"
50 ];
51 description = ''
52 Upstream resolver(s) to use as fallback for non-loki addresses.
53 Multiple values accepted.
54 '';
55 };
56 };
57
58 network = {
59 exit = mkOption {
60 type = bool;
61 default = false;
62 description = ''
63 Whether to act as an exit node. Beware that this
64 increases demand on the server and may pose liability concerns.
65 Enable at your own risk.
66 '';
67 };
68
69 exit-node = mkOption {
70 type = nullOr (listOf str);
71 default = null;
72 example = ''
73 exit-node = [ "example.loki" ]; # maps all exit traffic to example.loki
74 exit-node = [ "example.loki:100.0.0.0/24" ]; # maps 100.0.0.0/24 to example.loki
75 '';
76 description = ''
77 Specify a `.loki` address and an optional ip range to use as an exit broker.
78 See <http://probably.loki/wiki/index.php?title=Exit_Nodes> for
79 a list of exit nodes.
80 '';
81 };
82
83 keyfile = mkOption {
84 type = nullOr str;
85 default = null;
86 example = "snappkey.private";
87 description = ''
88 The private key to persist address with. If not specified the address will be ephemeral.
89 This keyfile is generated automatically if the specified file doesn't exist.
90 '';
91 };
92 };
93 };
94 };
95 default = { };
96 example = literalExpression ''
97 {
98 dns = {
99 bind = "127.3.2.1";
100 upstream = [ "1.1.1.1" "8.8.8.8" ];
101 };
102
103 network.exit-node = [ "example.loki" "example2.loki" ];
104 }
105 '';
106 description = ''
107 Configuration for Lokinet.
108 Currently, the best way to view the available settings is by
109 generating a config file using `lokinet -g`.
110 '';
111 };
112 };
113
114 config = mkIf cfg.enable {
115 networking.resolvconf.extraConfig = mkIf cfg.useLocally ''
116 name_servers="${cfg.settings.dns.bind}"
117 '';
118
119 systemd.services.lokinet = {
120 description = "Lokinet";
121 after = [
122 "network-online.target"
123 "network.target"
124 ];
125 wants = [
126 "network-online.target"
127 "network.target"
128 ];
129 wantedBy = [ "multi-user.target" ];
130
131 preStart = ''
132 ln -sf ${cfg.package}/share/bootstrap.signed ${dataDir}
133 ${pkgs.coreutils}/bin/install -m 600 ${configFile} ${dataDir}/lokinet.ini
134
135 ${optionalString (cfg.settings.network.keyfile != null) ''
136 ${pkgs.crudini}/bin/crudini --set ${dataDir}/lokinet.ini network keyfile "${dataDir}/${cfg.settings.network.keyfile}"
137 ''}
138 '';
139
140 serviceConfig = {
141 DynamicUser = true;
142 StateDirectory = "lokinet";
143 AmbientCapabilities = [
144 "CAP_NET_ADMIN"
145 "CAP_NET_BIND_SERVICE"
146 ];
147 ExecStart = "${cfg.package}/bin/lokinet ${dataDir}/lokinet.ini";
148 Restart = "always";
149 RestartSec = "5s";
150
151 # hardening
152 LockPersonality = true;
153 MemoryDenyWriteExecute = true;
154 NoNewPrivileges = true;
155 PrivateTmp = true;
156 PrivateMounts = true;
157 ProtectControlGroups = true;
158 ProtectHome = true;
159 ProtectHostname = true;
160 ProtectKernelLogs = true;
161 ProtectKernelModules = true;
162 ProtectKernelTunables = true;
163 ProtectSystem = "strict";
164 ReadWritePaths = "/dev/net/tun";
165 RestrictAddressFamilies = [
166 "AF_UNIX"
167 "AF_INET"
168 "AF_INET6"
169 "AF_NETLINK"
170 ];
171 RestrictNamespaces = true;
172 RestrictRealtime = true;
173 RestrictSUIDSGID = true;
174 };
175 };
176
177 environment.systemPackages = [ cfg.package ];
178 };
179}