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 = lib.mdDoc ''
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 = lib.mdDoc ''
46 Local addresses to which the server binds.
47 '';
48 };
49
50 port = mkOption {
51 type = types.port;
52 default = 8388;
53 description = lib.mdDoc ''
54 Port which the server uses.
55 '';
56 };
57
58 password = mkOption {
59 type = types.nullOr types.str;
60 default = null;
61 description = lib.mdDoc ''
62 Password for connecting clients.
63 '';
64 };
65
66 passwordFile = mkOption {
67 type = types.nullOr types.path;
68 default = null;
69 description = lib.mdDoc ''
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 = lib.mdDoc ''
78 Relay protocols.
79 '';
80 };
81
82 fastOpen = mkOption {
83 type = types.bool;
84 default = true;
85 description = lib.mdDoc ''
86 use TCP fast-open
87 '';
88 };
89
90 encryptionMethod = mkOption {
91 type = types.str;
92 default = "chacha20-ietf-poly1305";
93 description = lib.mdDoc ''
94 Encryption method. See <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 = literalExpression ''"''${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin"'';
102 description = lib.mdDoc ''
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 = lib.mdDoc ''
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 nameserver = "8.8.8.8";
121 };
122 description = lib.mdDoc ''
123 Additional configuration for shadowsocks that is not covered by the
124 provided options. The provided attrset will be serialized to JSON and
125 has to contain valid shadowsocks options. Unfortunately most
126 additional options are undocumented but it's easy to find out what is
127 available by looking into the source code of
128 <https://github.com/shadowsocks/shadowsocks-libev/blob/master/src/jconf.c>
129 '';
130 };
131 };
132
133 };
134
135
136 ###### implementation
137
138 config = mkIf cfg.enable {
139 assertions = singleton
140 { assertion = cfg.password == null || cfg.passwordFile == null;
141 message = "Cannot use both password and passwordFile for shadowsocks-libev";
142 };
143
144 systemd.services.shadowsocks-libev = {
145 description = "shadowsocks-libev Daemon";
146 after = [ "network.target" ];
147 wantedBy = [ "multi-user.target" ];
148 path = [ pkgs.shadowsocks-libev ] ++ optional (cfg.plugin != null) cfg.plugin ++ optional (cfg.passwordFile != null) pkgs.jq;
149 serviceConfig.PrivateTmp = true;
150 script = ''
151 ${optionalString (cfg.passwordFile != null) ''
152 cat ${configFile} | jq --arg password "$(cat "${cfg.passwordFile}")" '. + { password: $password }' > /tmp/shadowsocks.json
153 ''}
154 exec ss-server -c ${if cfg.passwordFile != null then "/tmp/shadowsocks.json" else configFile}
155 '';
156 };
157 };
158}