{ config, inputs, lib, pkgs, this, ... }: with lib; let cfg = config.nixfiles.modules.shadowsocks; in { options.nixfiles.modules.shadowsocks = { enable = mkEnableOption "Shadowsocks"; port = mkOption { type = with types; port; default = 8388; description = "Port."; }; }; config = mkIf cfg.enable { secrets.shadowsocks-json.file = "${inputs.self}/secrets/shadowsocks-json"; services.fail2ban.jails.shadowsocks = { enabled = true; settings = { filter = "shadowsocks"; inherit (cfg) port; }; }; systemd.services.shadowsocks = { description = "Shadowsocks"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { DynamicUser = true; RuntimeDirectory = "shadowsocks"; LoadCredential = "secret.json:${config.secrets.shadowsocks-json.path}"; ExecStartPre = let mergeJson = let configFile = pkgs.writeText "config.json" ( generators.toJSON { } { server = "::"; server_port = cfg.port; # Can't really use AEAD-2022[1] just yet because it's not # supported by some[2] clients. # # [1]: https://shadowsocks.org/doc/sip022.html # [2]: https://github.com/shadowsocks/ShadowsocksX-NG/issues/1480 # [2]: https://github.com/shadowsocks/shadowsocks-windows/issues/3448 # method = "2022-blake3-chacha20-poly1305"; method = "chacha20-ietf-poly1305"; password = null; # Must be set as a secret. users = null; # Muse be set as a secret. fast_open = true; acl = pkgs.writeText "block-internal-access.acl" '' [outbound_block_list] 0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.0.0.0/24 192.0.2.0/24 192.88.99.0/24 192.168.0.0/16 198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 224.0.0.0/4 240.0.0.0/4 255.255.255.255/32 ::1/128 ::ffff:127.0.0.1/104 fc00::/7 fe80::/10 ''; } ); in pkgs.writeShellScript "meregeJson" '' ${getExe pkgs.jq} \ -s '.[0] * .[1]' \ ${configFile} \ $CREDENTIALS_DIRECTORY/secret.json \ >$RUNTIME_DIRECTORY/config.json ''; in mergeJson; ExecStart = "${pkgs.shadowsocks-rust}/bin/ssserver --config \${RUNTIME_DIRECTORY}/config.json"; }; }; environment.etc = mkIf config.nixfiles.modules.fail2ban.enable { "fail2ban/filter.d/shadowsocks.conf".text = '' [Definition] failregex = ^.*tcp handshake failed.*\[::ffff:\].*$ ignoreregex = journalmatch = _SYSTEMD_UNIT=shadowsocks.service ''; }; networking.firewall.allowedTCPPorts = [ cfg.port ]; # https://github.com/shadowsocks/shadowsocks/wiki/Optimizing-Shadowsocks boot.kernel.sysctl = { "net.core.rmem_max" = mkOverride 100 (pow 2 26); "net.core.wmem_max" = mkOverride 100 (pow 2 26); "net.core.netdev_max_backlog" = pow 2 18; "net.core.somaxconn" = pow 2 12; "net.ipv4.tcp_syncookies" = 1; "net.ipv4.tcp_tw_reuse" = mkOverride 100 1; "net.ipv4.tcp_tw_recycle" = mkOverride 100 0; "net.ipv4.tcp_fin_timeout" = mkOverride 100 30; "net.ipv4.tcp_keepalive_time" = 60 * 20; "net.ipv4.ip_local_port_range" = "10000 65000"; "net.ipv4.tcp_max_syn_backlog" = pow 2 13; "net.ipv4.tcp_max_tw_buckets" = pow 2 12; "net.ipv4.tcp_fastopen" = mkOverride 100 3; "net.ipv4.tcp_mem" = mkOverride 100 (mkTcpMem 15 16 17); "net.ipv4.tcp_rmem" = mkOverride 100 (mkTcpMem 12 16 26); "net.ipv4.tcp_wmem" = mkOverride 100 (mkTcpMem 12 16 26); "net.ipv4.tcp_mtu_probing" = mkOverride 100 1; }; topology = with cfg; { nodes.${this.hostname}.services.shadowsocks = { name = "Shadowsocks"; icon = pkgs.fetchurl { url = "https://upload.wikimedia.org/wikipedia/commons/f/f5/Shadowsocks-Logo.svg"; hash = "sha256-NzGt0WQA4NQpMPsOTWgBrghuewxQeDoSe46oTm0f+BY="; }; details.listen.text = ":::${toString port}"; }; }; }; }