Commit 2981cec

HPCesia <me@hpcesia.com>
2026-06-14 09:41:10
Add proxy services
1 parent 6651794
Changed files (5)
modules/hosts/tribios/networking.nix
@@ -1,8 +1,24 @@
-{
+{den, ...}: {
+  den.hosts.tribios = {
+    settings.services.proxy.dae.interfaces = {
+      wan = ["auto"];
+      lan = ["br-lan"];
+    };
+  };
+
   den.aspects.tribios = {
+    includes = [
+      den.aspects.services.proxy
+    ];
+
     nixos = {
       boot.kernel.sysctl = {
         "net.ipv4.ip_forward" = 1;
+        "net.ipv4.conf.all.send_redirects" = 0;
+        "net.ipv4.conf.default.send_redirects" = 0;
+        "net.ipv4.conf.br-lan.send_redirects" = 0;
+        "net.ipv6.conf.all.forwarding" = 1;
+        "net.ipv6.conf.br-lan.forwarding" = 1;
       };
 
       # Tribios' network interfaces:
modules/services/proxy/dae.nix
@@ -0,0 +1,220 @@
+{
+  lib,
+  den,
+  ...
+}: {
+  den.aspects.services.proxy.dae = {
+    settings = {
+      interfaces = {
+        wan = lib.mkOption {
+          description = "The WAN interface to bind. Use it if you want to proxy localhost.";
+          type = lib.types.listOf lib.types.str;
+          default = ["auto"];
+        };
+        lan = lib.mkOption {
+          description = "The LAN interface to bind. Use it if you want to proxy LAN.";
+          type = lib.types.listOf lib.types.str;
+          default = [];
+        };
+      };
+    };
+
+    nixos = {
+      host,
+      pkgs,
+      config,
+      ...
+    }: let
+      cfg = host.settings.services.proxy.dae;
+
+      allHosts = builtins.concatMap builtins.attrValues (builtins.attrValues den.hosts);
+      otherHosts = builtins.filter (h: h.name != host.name) allHosts;
+
+      hostsWithIpv4ClearText = builtins.filter (h: h.address.ipv4.clearText != null) otherHosts;
+      hostsWithIpv4Secret = builtins.filter (h: h.address.ipv4.secret.name != null && h.address.ipv4.secret.file != null) otherHosts;
+      hostsWithIpv6ClearText = builtins.filter (h: h.address.ipv6.clearText != null) otherHosts;
+      hostsWithIpv6Secret = builtins.filter (h: h.address.ipv6.secret.name != null && h.address.ipv6.secret.file != null) otherHosts;
+
+      clearTextIps =
+        (map (h: h.address.ipv4.clearText) hostsWithIpv4ClearText)
+        ++ (map (h: h.address.ipv6.clearText) hostsWithIpv6ClearText);
+
+      secretIpPlaceholders =
+        (map (h: config.vaultix.placeholder.${h.address.ipv4.secret.name}) hostsWithIpv4Secret)
+        ++ (map (h: config.vaultix.placeholder.${h.address.ipv6.secret.name}) hostsWithIpv6Secret);
+
+      allHostIps = clearTextIps ++ secretIpPlaceholders;
+
+      hostDirectRule =
+        lib.optionalString (allHostIps != [])
+        "dip(${builtins.concatStringsSep ", " allHostIps}) -> direct";
+
+      v2ray-geoip = pkgs.fetchurl rec {
+        pname = "v2ray-geoip";
+        version = "202606262300";
+        url = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/${version}/geoip.dat";
+        downloadToTemp = true;
+        recursiveHash = true;
+        postFetch = ''
+          install -D "$downloadedFile" "$out/share/v2ray/geoip.dat"
+        '';
+        hash = "sha256-0z0qNvvN8I5PY14DWZgk4blZcgLoWIChZefkgeuWM1c=";
+      };
+      v2ray-geosite = pkgs.fetchurl rec {
+        pname = "v2ray-geosite";
+        version = "202606262300";
+        url = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/${version}/geosite.dat";
+        downloadToTemp = true;
+        recursiveHash = true;
+        postFetch = ''
+          install -D "$downloadedFile" "$out/share/v2ray/geosite.dat"
+        '';
+        hash = "sha256-NytCOfetMwjKFlc8bEz31KskcSnEkvSmvTp+Y8RvVC0=";
+      };
+    in {
+      services.dae = {
+        enable = true;
+        package = pkgs.dae.overrideAttrs {
+          version = "1.1.0";
+          src = pkgs.fetchFromGitHub {
+            owner = "daeuniverse";
+            repo = "dae";
+            tag = "v1.1.0";
+            hash = "sha256-Kc51VQuqObxKXVXGv5CnDm4/3XYqjPvrpAQSVb2vxSM=";
+            fetchSubmodules = true;
+          };
+          vendorHash = "sha256-juxIsZt1T33epN8CbzDc02MmlW5PtYa4pcGxuX9OpH4=";
+        };
+        assets = [v2ray-geoip v2ray-geosite];
+        config = ''
+          include {
+            # For security reasons, dae requires included files to be under the entry config directory.
+            routing.dae
+          }
+
+          global {
+            ${
+            if cfg.interfaces.wan != []
+            then "wan_interface: ${builtins.concatStringsSep "," cfg.interfaces.wan}"
+            else ""
+          }
+            ${
+            if cfg.interfaces.lan != []
+            then "lan_interface: ${builtins.concatStringsSep "," cfg.interfaces.lan}"
+            else ""
+          }
+            log_level: info
+            allow_insecure: false
+            check_interval: 60s
+            check_tolerance: 100ms
+            dial_mode: domain
+          }
+
+          dns {
+            upstream {
+              alidns: 'udp://dns.alidns.com:53'
+              googledns: 'tcp+udp://dns.google:53'
+            }
+            routing {
+              request {
+                qname(geosite:category-ads-all) -> reject
+                qtype(https) -> reject
+                qname(geosite:cn) -> alidns
+                fallback: googledns
+              }
+            }
+          }
+
+          subscription {
+            common: "http://127.0.0.1:${toString host.settings.services.proxy.sub-store.port}/backend/download/collection/common?target=V2Ray"
+            auxiliary: "http://127.0.0.1:${toString host.settings.services.proxy.sub-store.port}/backend/download/collection/auxiliary?target=V2Ray"
+          }
+
+          group {
+            proxy {
+              filter: !subtag(auxiliary) && name(regex: '香港|台湾|美国|日本|新加坡')
+              filter: subtag(auxiliary) && name(regex: '香港|台湾|美国|日本|新加坡') [add_latency: 300ms]
+              policy: min_moving_avg
+            }
+            proxy_download {
+              filter: !subtag(auxiliary) && !name(regex: 'IEPL|家宽') && name(regex: '香港|美国|日本|新加坡|韩国|德国')
+              policy: min_avg10
+            }
+          }
+        '';
+      };
+
+      systemd.services.dae = {
+        serviceConfig.LoadCredential = ["routing.dae:${config.vaultix.templates.dae-routing.path}"];
+      };
+
+      vaultix.secrets = lib.mkMerge (
+        (map (h: {${h.address.ipv4.secret.name}.file = h.address.ipv4.secret.file;}) hostsWithIpv4Secret)
+        ++ (map (h: {${h.address.ipv6.secret.name}.file = h.address.ipv6.secret.file;}) hostsWithIpv6Secret)
+      );
+
+      vaultix.templates.dae-routing = {
+        content = ''
+          routing {
+            # == System ==
+            pname(NetworkManager, systemd-networkd, systemd-resolved,
+                  dnsmasq, cloudflared) -> must_direct
+            pname(ssh) -> direct
+            dport(22) -> direct
+
+            # == DNS ==
+            dip(223.5.5.5, 223.6.6.6) -> direct
+            domain(full:dns.alidns.com) -> direct
+            dip(119.29.29.29, 119.28.28.28) -> direct
+            domain(full:doh.pub) -> direct
+            dip(8.8.8.8, 8.8.4.4) -> proxy
+            domain(full:dns.google) -> proxy
+            dip(1.1.1.1, 1.0.0.1) -> proxy
+            domain(full:one.one.one.one, full:cloudflare-dns.com) -> proxy
+
+            # == Private ==
+            dip(geoip:private) -> direct
+            domain(geosite:private) -> direct
+
+            # == My Hosts ==
+            ${hostDirectRule}
+
+            # == AI ==
+            domain(geosite:category-ai-!cn) -> proxy
+
+            # == Big Company ==
+            domain(geosite:microsoft@cn) -> direct
+            domain(geosite:microsoft) -> proxy
+            domain(geosite:apple@cn) -> direct
+            domain(geosite:apple) -> proxy
+            domain(geosite:google@cn) -> direct
+            domain(geosite:google) -> proxy
+            domain(geosite:cloudflare) -> proxy
+
+            # == Game ==
+            domain(suffix:cm.steampowered.com) -> direct
+            domain(suffix:steamserver.net) -> direct
+            domain(geosite:steam@cn) -> direct
+            domain(geosite:category-games-cn) -> direct
+            domain(geosite:category-game-platforms-download@cn) -> direct
+
+            # == Download ==
+            domain(geosite:mega) -> proxy_download
+            domain(keyword:dl-a10b-, dl-z01a-, dl-b07-) -> proxy_download
+
+            # == China ==
+            domain(geosite:tld-cn, geosite:geolocation-cn, geosite:cn) -> direct
+            dip(geoip:cn) -> direct
+            domain(geosite:gfw) -> proxy
+
+            # == Other ==
+            domain(suffix: kagi.com, suffix: codeberg.org, suffix: mxrouting.net) -> direct
+
+            # == Fallback ==
+            fallback: proxy
+          }
+        '';
+      };
+    };
+  };
+}
modules/services/proxy/default.nix
@@ -0,0 +1,8 @@
+{den, ...}: {
+  den.aspects.services.proxy = {
+    includes = [
+      den.aspects.services.proxy.dae
+      den.aspects.services.proxy.sub-store
+    ];
+  };
+}
modules/services/proxy/sub-store.nix
@@ -0,0 +1,44 @@
+{
+  lib,
+  inputs,
+  ...
+}: {
+  den.aspects.services.proxy.sub-store = {
+    settings = {
+      port = lib.mkOption {
+        default = 3000;
+        type = lib.types.port;
+        description = "Port the sub-store frontend listen on";
+      };
+    };
+
+    persist = {
+      directories = [
+        {
+          directory = "/var/lib/private/sub-store";
+          user = "nobody";
+          group = "nogroup";
+          mode = "0755";
+        }
+      ];
+    };
+
+    nixos = {host, ...}: let
+      cfg = host.settings.services.proxy.sub-store;
+    in {
+      imports = [inputs.nur-hpcesia.nixosModules.sub-store];
+
+      services.sub-store = {
+        enable = true;
+        port = cfg.port;
+        frontend = {
+          enable = true;
+          port = cfg.port;
+          # sub-store uses the URL path for authentication.
+          # Since I only access it locally, I've made this path public.
+          backendPath = "/backend";
+        };
+      };
+    };
+  };
+}
secrets/cache/tribios/933dc774c4b9926ee79cc113303524f3872b2da03f5572ffb2dc6e2d900102fe
@@ -0,0 +1,7 @@
+age-encryption.org/v1
+-> ssh-ed25519 1YGZAA TPMeCzIQkFN6nXjYD6v/7GMnK6q6HEIm9Kw5iq+10Xg
+C4RxyLuQ+MYTF7Qc9v243mkx1p12tfhnMvzy8eihAHk
+-> *Z-grease SW Ea'*@~s $h:&KAsK xKq
+uu8hbA
+--- DJkoXZ8Vrs/nOKODiGBhmnYZAwLKCUliir2imm+ou/A
+�Ǯq���y�1Z��D�Õ|�/˨�]3�1�z�_Dr��:�k�~�
\ No newline at end of file