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