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