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