1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.shadowsocks;
7
8 opts = {
9 server = cfg.localAddress;
10 server_port = cfg.port;
11 method = cfg.encryptionMethod;
12 mode = cfg.mode;
13 user = "nobody";
14 fast_open = cfg.fastOpen;
15 } // optionalAttrs (cfg.plugin != null) {
16 plugin = cfg.plugin;
17 plugin_opts = cfg.pluginOpts;
18 } // optionalAttrs (cfg.password != null) {
19 password = cfg.password;
20 } // cfg.extraConfig;
21
22 configFile = pkgs.writeText "shadowsocks.json" (builtins.toJSON opts);
23
24in
25
26{
27
28 ###### interface
29
30 options = {
31
32 services.shadowsocks = {
33
34 enable = mkOption {
35 type = types.bool;
36 default = false;
37 description = ''
38 Whether to run shadowsocks-libev shadowsocks server.
39 '';
40 };
41
42 localAddress = mkOption {
43 type = types.coercedTo types.str singleton (types.listOf types.str);
44 default = [ "[::0]" "0.0.0.0" ];
45 description = ''
46 Local addresses to which the server binds.
47 '';
48 };
49
50 port = mkOption {
51 type = types.int;
52 default = 8388;
53 description = ''
54 Port which the server uses.
55 '';
56 };
57
58 password = mkOption {
59 type = types.nullOr types.str;
60 default = null;
61 description = ''
62 Password for connecting clients.
63 '';
64 };
65
66 passwordFile = mkOption {
67 type = types.nullOr types.path;
68 default = null;
69 description = ''
70 Password file with a password for connecting clients.
71 '';
72 };
73
74 mode = mkOption {
75 type = types.enum [ "tcp_only" "tcp_and_udp" "udp_only" ];
76 default = "tcp_and_udp";
77 description = ''
78 Relay protocols.
79 '';
80 };
81
82 fastOpen = mkOption {
83 type = types.bool;
84 default = true;
85 description = ''
86 use TCP fast-open
87 '';
88 };
89
90 encryptionMethod = mkOption {
91 type = types.str;
92 default = "chacha20-ietf-poly1305";
93 description = ''
94 Encryption method. See <link xlink:href="https://github.com/shadowsocks/shadowsocks-org/wiki/AEAD-Ciphers"/>.
95 '';
96 };
97
98 plugin = mkOption {
99 type = types.nullOr types.str;
100 default = null;
101 example = "\${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin";
102 description = ''
103 SIP003 plugin for shadowsocks
104 '';
105 };
106
107 pluginOpts = mkOption {
108 type = types.str;
109 default = "";
110 example = "server;host=example.com";
111 description = ''
112 Options to pass to the plugin if one was specified
113 '';
114 };
115
116 extraConfig = mkOption {
117 type = types.attrs;
118 default = {};
119 example = ''
120 {
121 nameserver = "8.8.8.8";
122 }
123 '';
124 description = ''
125 Additional configuration for shadowsocks that is not covered by the
126 provided options. The provided attrset will be serialized to JSON and
127 has to contain valid shadowsocks options. Unfortunately most
128 additional options are undocumented but it's easy to find out what is
129 available by looking into the source code of
130 <link xlink:href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/src/jconf.c"/>
131 '';
132 };
133 };
134
135 };
136
137
138 ###### implementation
139
140 config = mkIf cfg.enable {
141 assertions = singleton
142 { assertion = cfg.password == null || cfg.passwordFile == null;
143 message = "Cannot use both password and passwordFile for shadowsocks-libev";
144 };
145
146 systemd.services.shadowsocks-libev = {
147 description = "shadowsocks-libev Daemon";
148 after = [ "network.target" ];
149 wantedBy = [ "multi-user.target" ];
150 path = [ pkgs.shadowsocks-libev ] ++ optional (cfg.plugin != null) cfg.plugin ++ optional (cfg.passwordFile != null) pkgs.jq;
151 serviceConfig.PrivateTmp = true;
152 script = ''
153 ${optionalString (cfg.passwordFile != null) ''
154 cat ${configFile} | jq --arg password "$(cat "${cfg.passwordFile}")" '. + { password: $password }' > /tmp/shadowsocks.json
155 ''}
156 exec ss-server -c ${if cfg.passwordFile != null then "/tmp/shadowsocks.json" else configFile}
157 '';
158 };
159 };
160}