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 = true;
15 } // optionalAttrs (cfg.password != null) { password = cfg.password; };
16
17 configFile = pkgs.writeText "shadowsocks.json" (builtins.toJSON opts);
18
19in
20
21{
22
23 ###### interface
24
25 options = {
26
27 services.shadowsocks = {
28
29 enable = mkOption {
30 type = types.bool;
31 default = false;
32 description = ''
33 Whether to run shadowsocks-libev shadowsocks server.
34 '';
35 };
36
37 localAddress = mkOption {
38 type = types.str;
39 default = "0.0.0.0";
40 description = ''
41 Local address to which the server binds.
42 '';
43 };
44
45 port = mkOption {
46 type = types.int;
47 default = 8388;
48 description = ''
49 Port which the server uses.
50 '';
51 };
52
53 password = mkOption {
54 type = types.nullOr types.str;
55 default = null;
56 description = ''
57 Password for connecting clients.
58 '';
59 };
60
61 passwordFile = mkOption {
62 type = types.nullOr types.path;
63 default = null;
64 description = ''
65 Password file with a password for connecting clients.
66 '';
67 };
68
69 mode = mkOption {
70 type = types.enum [ "tcp_only" "tcp_and_udp" "udp_only" ];
71 default = "tcp_and_udp";
72 description = ''
73 Relay protocols.
74 '';
75 };
76
77 encryptionMethod = mkOption {
78 type = types.str;
79 default = "chacha20-ietf-poly1305";
80 description = ''
81 Encryption method. See <link xlink:href="https://github.com/shadowsocks/shadowsocks-org/wiki/AEAD-Ciphers"/>.
82 '';
83 };
84
85 };
86
87 };
88
89
90 ###### implementation
91
92 config = mkIf cfg.enable {
93 assertions = singleton
94 { assertion = cfg.password == null || cfg.passwordFile == null;
95 message = "Cannot use both password and passwordFile for shadowsocks-libev";
96 };
97
98 systemd.services.shadowsocks-libev = {
99 description = "shadowsocks-libev Daemon";
100 after = [ "network.target" ];
101 wantedBy = [ "multi-user.target" ];
102 path = [ pkgs.shadowsocks-libev ] ++ optional (cfg.passwordFile != null) pkgs.jq;
103 serviceConfig.PrivateTmp = true;
104 script = ''
105 ${optionalString (cfg.passwordFile != null) ''
106 cat ${configFile} | jq --arg password "$(cat "${cfg.passwordFile}")" '. + { password: $password }' > /tmp/shadowsocks.json
107 ''}
108 exec ss-server -c ${if cfg.passwordFile != null then "/tmp/shadowsocks.json" else configFile}
109 '';
110 };
111 };
112}