at 23.11-pre 4.4 kB view raw
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}