Commit e2a586a

HPCesia <me@hpcesia.com>
2026-08-27 10:05:48
xray: init
1 parent 9f83c5f
.secrets/cache/hyacine/23b574ec5c5064908ea2bdb8c99bcce46094933cc0690a84e369a09027135ef6
@@ -0,0 +1,7 @@
+age-encryption.org/v1
+-> ssh-ed25519 diaafw ylUbkqPM7WrNZ8oZlNcwRw5HquVV8C38vpXzzFBOEEk
+3CrcjoTTvL1u58XAIYnhvzP1UqRWkmqBn9S/fuw6dqg
+-> J-grease
+3P4UdACi4XQdX98
+--- S5KEZjOwcn2GF5Ct5HtvCCWPG6kvMqjYdl1V1pfppj0
+��H�(�������J[��lu×�Cz3�w�&h@ȿw��W��E5�|\�i���(s:
����Ĺ9����
\ No newline at end of file
.secrets/cache/hyacine/c1f79e3dc3b16a58a93265681333e2188971d6add779082c0a536868ac1b471e
Binary file
.secrets/cache/hyacine/cfee81e34551a6f48db8023780a2ae1fd06fb0683e0ad303f06c8821c9878bee
@@ -0,0 +1,9 @@
+age-encryption.org/v1
+-> ssh-ed25519 diaafw 1CrplH6pGtg3e3rZZ/65eUrt4aAnfCsRNcbbB3J1CQo
+SWgoSLKCQxPwZv6Q6hsZYUvnAuC4U6hkFNc3tCnhl4I
+-> #PeT,-grease 6v/6P1 P ZLWS#qy jQjo'HfJ
+ciuZeNMWoExP4m6GX0zVcTvTRGluqKtNzo0rxkU0HSXzo/vkSCjzNJJ6/e4ZDlRc
+Rju5bJc
+--- u4efXM0qsu19E20Pzqw+dTva8V7vJ8uBczZbE5jGHcg
+��^%i��w�ʛ鎼��(g
+��������S�!k�L�����
\ No newline at end of file
.secrets/cache/kevin/6cfa3578e01638f1c8ce9729e6db68ab155162f46672ed972d27960870a131af
@@ -0,0 +1,8 @@
+age-encryption.org/v1
+-> ssh-ed25519 WM7kiQ Wlc/u+8hS9OXIJSGPj8/ldBi7iqTOMn7CpbvGDxiwjE
+G5G0c7YQOKAPFxjYEFL00lp+A+3+misHNKCMcguvC1k
+-> zS-grease " tUc
+Jvh8UOJJBMfoiRn4oVCXHntxVS/T94L70O0GV/mvjxW4VQ
+--- AO+kEtuKF1x3lWaSzq+HA2kkKQy5M6FsDe2OvbPgbKg
+�zQo�����t�\��4Ȗ �P�.�.�p���g�ğ�r�>Y�kV/���0�`�p`
+���(`r%�� 
\ No newline at end of file
.secrets/cache/kevin/94ab301970e7a4c4159d081863efb003040031ec46b413e6219fa9223f17d7f4
Binary file
.secrets/cache/kevin/cbe6bb4ca091e1e324e95593330f59278156212abf2c74f4303daeecb4ae5a50
@@ -0,0 +1,9 @@
+age-encryption.org/v1
+-> ssh-ed25519 WM7kiQ UBEj2NuJzbisSKRbImnhZv7ihiDU1QvEzw05HiWCbRw
+Nq2Y8D3JQ5fnNCF+CFAegCTSkYmuIYA6wqz32mWwAb8
+-> y/E{D-grease jy2Wr~ v'/.9p cgi,<T5
+SiLj6u61soeHfHWPBCgFf2JV0080ap1Sw4ZLzWJhNJi6vWqZalepBGKAT5mFAodN
+HG88UOSc1S+7eJIh1iYXLlKg4u5VHL0zWSj2i6i9sdzuCGNJHw
+--- coGd/U1ekfCFGbmjyiPNfjlWwPaaQlKx9x1Akmk9/CI
+��f>\z����.�E}M���o
+�}�Ѯ�/�*�#��c/���
\ No newline at end of file
.secrets/cache/tribios/16a9fb3d720054c9fd42780aa0439bd5cc7ea034afcf8c5322884977a9cdb648
@@ -0,0 +1,7 @@
+age-encryption.org/v1
+-> ssh-ed25519 1YGZAA 5x8Is7qlT8S10Q4E50Hp6iZOOakmS03RqM/XWEOoMRk
+u+ciBmMYyq+A4NRqG9gI3HS7MCC47iwArYP/U7/S2bM
+-> A&Yc&-grease BIB Y67B)?Z) j5 (N
+/pjZKloAWrq96VyapUr4hmArqqGrzo6egGQyzf8
+--- z5OFpysoNWbVI3om5DM997XW6CEwnaZeYxFWTC5GEpo
+d�e�*͂(Gx �'���>��=A]{��?-��Uq�#�
\ No newline at end of file
.secrets/cache/tribios/c1c305dbe1b886e2ae8b65b5d54f43a7f3692f83b8f6f0e5c48adcfff7882d15
Binary file
.secrets/cache/tribios/d5c9fd66af4de3dbda0e7b12f370c008b892cf4b6984f3ae18d866aeadd75251
Binary file
modules/hosts/hyacine/services/default.nix
@@ -5,6 +5,14 @@
         domain = "derp-hyacine.trin.one";
         port = 8010;
       };
+
+      services.xray = {
+        realitySiteFileAged = ./xray-reality-site.age;
+        port = 7890;
+        uuidFileAged = ./xray-uuid.age;
+        privateKeyFileAged = ./xray-private-key.age;
+        pubKeyFileAged = ./xray-pub-key.age;
+      };
     };
   };
 
@@ -12,5 +20,8 @@
     services.caddy
 
     services.tailscale-derp
+
+    services.caddy.reality
+    services.xray
   ];
 }
modules/hosts/hyacine/services/xray-private-key.age
@@ -0,0 +1,7 @@
+age-encryption.org/v1
+-> piv-p256 xCEwtQ AlTB9Zft07Z5nVbPZ7Z6PJS0ZqzG86F0Pvm65THv1s9g
+IapezFo5boewoC8b8Z4pz19m4yFN6cctvszmWDwcpc0
+-> OA"-grease ,C
+HnUnUCUeJTv0tKXM
+--- WoYQ+3oOwrKQ9ic6CzXkdyOf/CgzWipeeyd95iGKREw
+����&Ø�������"�Qx�eݪ<�a�F���C%�8qK�%a� T����2�&\ta���?ݴ�I�?�U DJ�MA�
\ No newline at end of file
modules/hosts/hyacine/services/xray-pub-key.age
@@ -0,0 +1,7 @@
+age-encryption.org/v1
+-> piv-p256 xCEwtQ Ak/a7dzv2fbDmrywFPMNCWIgNy7MdA2C9KRFLbfICKBU
+cwxGJ0HR477HNVilPSVVK+nFqywPiiH9j6zDtykt+HM
+-> $R.-grease
+4XD3FyTa2LE8Bg
+--- r0zvDBbbC5cH8bh0S/qLuGYRoHSiZWUuBBoh/Vg9GOU
+�dXJ*�
4&�#guF�K��}���(zb��u�8�FϤ�V7�ܱ!f��k?�4��D���V6�Ǒ��ra�=�lWbFo�
\ No newline at end of file
modules/hosts/hyacine/services/xray-reality-site.age
@@ -0,0 +1,7 @@
+age-encryption.org/v1
+-> piv-p256 xCEwtQ AveEZ9Rk1s218t8QXZUJtgJO3dt89Pox5ZmwnINjO2kJ
+Bfx1zFy5GST7j+Q+N7T8QxcD8KXwCbEsqZb+HR+z2kA
+-> oL-grease
+pQse5EsijGKlZ+lxEABvbAonr5TIR3H+t5VSD6VkplsxGO0MKjdTAAL/cSljYw
+--- ioBb1bIlLetD2f03S0FoVC0GWBW9WlIIfLsvyGkIOsY
+�q�($������\���KΆ�$�8]��tp����h�"CՅ
\ No newline at end of file
modules/hosts/hyacine/services/xray-uuid.age
@@ -0,0 +1,8 @@
+age-encryption.org/v1
+-> piv-p256 xCEwtQ AvAC/bDGO+TuTyc7UtoC0D3eM1/TIC4+a/5DVQITd+pK
+oVDLCW6kj/7DljYWLPXQqHhdQDlSa1nw3CZVUViWe2k
+-> qBeVEk:5-grease m+^goT
+KxxxIX6dV0J9IALWHsgUd7itTxWjCt85bBEidB1PmQYHXAjPGGLnzF5sO1168dnR
+fK6EVv3ZBXse5RWzRROwDjTSJfaDL0tBmNWf73m9VMe1qPXpVw
+--- PgQ5H0O3UUrHxFc0Is9QNDKCKG54wmwP6kASpLpTZ2o
+]m)����C��4�x�TCb'�8yo��)o��b�ؠ��2������E��&�n��g]2�YJ��mJu)q
\ No newline at end of file
modules/services/caddy/default.nix
@@ -21,8 +21,9 @@
         package = pkgs.caddy.withPlugins {
           plugins = [
             "github.com/caddy-dns/cloudflare@v0.2.4"
+            "github.com/mholt/caddy-l4@v0.1.2"
           ];
-          hash = "sha256-7GoH8YLCoPmPExQxoga2FHB58zQDoZVf1BBwkVi0SsQ=";
+          hash = "sha256-6HJkIfqacmsEaubClOVjzPM+7Jy5Z7xHVORzf6+5OxU=";
         };
 
         globalConfig = ''
modules/services/caddy/quirks.nix
@@ -16,7 +16,7 @@ in {
         path = "/foo/bar" # Path to reverse proxy to
         stripPath = false; # Whether to strip path prefix ("/foo/bar")
         upstream = "/"; Path to reverse proxy from
-        prefer = "direct"; # Connection policy override: null (host default), "cdn", "direct" or "tailscale"
+        prefer = "direct"; # Connection policy override: null (host default), "direct" or "tailscale"
       }
   '';
 
modules/services/caddy/reality.nix
@@ -0,0 +1,52 @@
+{
+  den.aspects.services.caddy.reality = {
+    nixos = {
+      host,
+      config,
+      ...
+    }: let
+      xrayCfg = host.settings.services.xray;
+    in {
+      boot.kernel.sysctl = {
+        "net.ipv4.tcp_congestion_control" = "bbr";
+      };
+
+      vaultix.secrets.reality-site.file = xrayCfg.realitySiteFileAged;
+
+      vaultix.templates.caddy-reality = {
+        content = ''
+          :${toString config.services.caddy.httpsPort} {
+            @reality tls sni ${config.vaultix.placeholder.reality-site}
+            route @reality {
+              proxy {
+                upstream 127.0.0.1:${toString xrayCfg.port}
+              }
+            }
+            route {
+              proxy {
+                upstream 127.0.0.1:${toString config.services.caddy.httpsPort}
+              }
+            }
+          }
+        '';
+        owner = config.services.caddy.user;
+        group = config.services.caddy.group;
+        mode = "0400";
+      };
+
+      services.caddy.globalConfig = ''
+        default_bind 127.0.0.1
+        layer4 {
+          :${toString config.services.caddy.httpPort} {
+            route {
+              proxy {
+                upstream 127.0.0.1:${toString config.services.caddy.httpPort}
+              }
+            }
+          }
+          import ${config.vaultix.templates.caddy-reality.path}
+        }
+      '';
+    };
+  };
+}
modules/services/direct-dns/_lib.nix
@@ -26,7 +26,7 @@
     mode =
       if prefer != null
       then prefer
-      else host.settings.services.direct-dns.mode or "cdn";
+      else host.settings.services.direct-dns.mode or null;
     v4 =
       if mode == "direct"
       then fromClearOrSecret host.address.ipv4
modules/services/direct-dns/default.nix
@@ -2,14 +2,14 @@
   den.aspects.services.direct-dns = {
     settings.host = {
       mode = lib.mkOption {
-        type = lib.types.enum ["cdn" "direct" "tailscale"];
-        default = "cdn";
+        type = lib.types.nullOr (lib.types.enum ["direct" "tailscale"]);
+        default = null;
         description = ''
           Default connection policy for reverse proxy domains served by this host.
 
-          - `cdn`: resolve through public DNS (unchanged).
-          - `direct`: resolve to the host's public IP.
-          - `tailscale`: resolve to the host's tailscale IP.
+          - `null`: resolve through public DNS (unchanged).
+          - `"direct"`: resolve to the host's public IP.
+          - `"tailscale"`: resolve to the host's tailscale IP.
 
           Individual reverse proxy entries can override this with `prefer`.
         '';
modules/services/mihomo/proxies.nix
@@ -1,6 +1,9 @@
-{
+{den, ...}: {
   den.aspects.services.mihomo.proxies = {
-    nixos = {config, ...}: {
+    nixos = {config, ...}: let
+      allHosts = builtins.concatMap builtins.attrValues (builtins.attrValues den.hosts);
+      hyacine = builtins.head (builtins.filter (h: h.name == "hyacine") allHosts);
+    in {
       services.mihomo = {
         config = {
           proxy-providers = let
@@ -30,6 +33,23 @@
               };
           };
           proxies = [
+            {
+              name = "[Self]HK-Hyacine";
+              type = "vless";
+              server._secret = config.vaultix.secrets.${hyacine.address.ipv4.secret.name}.path;
+              port = 443;
+              uuid._secret = config.vaultix.secrets.hyacine-xray-uuid.path;
+              network = "tcp";
+              udp = true;
+              tls = true;
+              flow = "xtls-rprx-vision";
+              servername._secret = config.vaultix.secrets.hyacine-xray-reality-site.path;
+              client-fingerprint = "chrome";
+              reality-opts = {
+                public-key._secret = config.vaultix.secrets.hyacine-xray-pub-key.path;
+                short-id = "";
+              };
+            }
           ];
         };
       };
@@ -45,6 +65,22 @@
           owner = "mihomo";
           group = "mihomo";
         };
+
+        hyacine-xray-uuid = {
+          file = hyacine.settings.services.xray.uuidFileAged;
+          owner = "mihomo";
+          group = "mihomo";
+        };
+        hyacine-xray-pub-key = {
+          file = hyacine.settings.services.xray.pubKeyFileAged;
+          owner = "mihomo";
+          group = "mihomo";
+        };
+        hyacine-xray-reality-site = {
+          file = hyacine.settings.services.xray.realitySiteFileAged;
+          owner = "mihomo";
+          group = "mihomo";
+        };
       };
     };
   };
modules/services/xray/_module.nix
@@ -0,0 +1,114 @@
+{
+  lib,
+  pkgs,
+  config,
+  ...
+}: let
+  utils = pkgs.callPackage "${pkgs.path}/nixos/lib/utils.nix" {};
+
+  cfg = config.services.xray;
+
+  configFormat = pkgs.formats.json {};
+
+  runtimeConfig = "/run/xray/config.json";
+in {
+  disabledModules = ["services/networking/xray.nix"];
+
+  options.services.xray = {
+    enable = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = ''
+        Whether to run xray server.
+
+        Either `settingsFile` or `settings` must be specified.
+      '';
+    };
+
+    package = lib.mkPackageOption pkgs "xray" {};
+
+    settingsFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.path;
+      default = null;
+      example = "/etc/xray/config.json";
+      description = ''
+        The absolute path to the configuration file.
+
+        Either `settingsFile` or `settings` must be specified.
+
+        See <https://www.v2fly.org/en_US/config/overview.html>.
+      '';
+    };
+
+    settings = lib.mkOption {
+      type = lib.types.nullOr (lib.types.submodule {
+        freeformType = configFormat.type;
+      });
+      default = null;
+      example = {
+        inbounds = [
+          {
+            port = 1080;
+            listen = "127.0.0.1";
+            protocol = "http";
+          }
+        ];
+        outbounds = [
+          {
+            protocol = "freedom";
+          }
+        ];
+      };
+      description = ''
+        The configuration object.
+
+        Either `settingsFile` or `settings` must be specified.
+
+        See <https://www.v2fly.org/en_US/config/overview.html>.
+
+        Options containing secret data should be set to an attribute set
+        containing the attribute `_secret` - a string pointing to a file
+        containing the value the option should be set to.
+
+        Set `quote = true` (default behavior) to quote the content of the
+        secret file as a string, or set `quote = false` to parse the content
+        of the secret file to JSON.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = (cfg.settingsFile == null) != (cfg.settings == null);
+        message = "Either but not both `settingsFile` and `settings` should be specified for xray.";
+      }
+    ];
+
+    systemd.services.xray = {
+      description = "xray Daemon";
+      after = ["network.target"];
+      wantedBy = ["multi-user.target"];
+      serviceConfig = {
+        ExecStart = "${lib.getExe cfg.package} run -config ${runtimeConfig}";
+
+        ExecStartPre = "+${pkgs.writeShellScript "xray-pre-start" ''
+          ${lib.optionalString (cfg.settings != null) (utils.genJqSecretsReplacementSnippet cfg.settings runtimeConfig)}
+          ${lib.optionalString (cfg.settings != null) "chown --reference=/run/xray ${runtimeConfig}"}
+          ${lib.getExe cfg.package} -test -config ${
+            if cfg.settings != null
+            then runtimeConfig
+            else cfg.settingsFile
+          }
+        ''}";
+
+        DynamicUser = true;
+        RuntimeDirectory = "xray";
+        RuntimeDirectoryMode = "0700";
+        CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
+        AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
+        NoNewPrivileges = true;
+      };
+    };
+  };
+}
modules/services/xray/default.nix
@@ -0,0 +1,116 @@
+{lib, ...}: {
+  den.aspects.services.xray = {
+    settings.host = {
+      realitySiteFileAged = lib.mkOption {
+        type = lib.types.path;
+      };
+      port = lib.mkOption {
+        type = lib.types.port;
+        default = 7890;
+      };
+      uuidFileAged = lib.mkOption {
+        type = lib.types.path;
+      };
+      privateKeyFileAged = lib.mkOption {
+        type = lib.types.path;
+      };
+      pubKeyFileAged = lib.mkOption {
+        type = lib.types.path;
+      };
+    };
+
+    nixos = {
+      host,
+      config,
+      ...
+    }: let
+      cfg = host.settings.services.xray;
+    in {
+      imports = [./_module.nix];
+
+      vaultix.secrets = {
+        xray-uuid.file = cfg.uuidFileAged;
+        xray-private-key.file = cfg.privateKeyFileAged;
+        xray-reality-site.file = cfg.realitySiteFileAged;
+      };
+
+      vaultix.templates.xray-reality-target = {
+        content = "${config.vaultix.placeholder.xray-reality-site}:443";
+        owner = "root";
+        group = "root";
+        mode = "0400";
+      };
+
+      services.xray = {
+        enable = true;
+        settings = {
+          log.loglevel = "warning";
+          routing = {
+            rules = [
+              {
+                type = "field";
+                protocol = ["bittorrent"];
+                outboundTag = "block";
+              }
+              {
+                type = "field";
+                ip = ["geoip:private"];
+                outboundTag = "block";
+              }
+            ];
+          };
+          inbounds = [
+            {
+              listen = "127.0.0.1";
+              port = cfg.port;
+              protocol = "vless";
+              sniffing = {
+                enabled = true;
+                destOverride = ["http" "tls" "quic"];
+              };
+              settings = {
+                clients = [
+                  {
+                    id = {
+                      _secret = config.vaultix.secrets.xray-uuid.path;
+                    };
+                    flow = "xtls-rprx-vision";
+                  }
+                ];
+                decryption = "none";
+              };
+              streamSettings = {
+                network = "raw";
+                security = "reality";
+                realitySettings = {
+                  target = {
+                    _secret = config.vaultix.templates.xray-reality-target.path;
+                  };
+                  serverNames = [
+                    {
+                      _secret = config.vaultix.secrets.xray-reality-site.path;
+                    }
+                  ];
+                  privateKey = {
+                    _secret = config.vaultix.secrets.xray-private-key.path;
+                  };
+                  shortIds = [""];
+                };
+              };
+            }
+          ];
+          outbounds = [
+            {
+              tag = "direct";
+              protocol = "freedom";
+            }
+            {
+              tag = "block";
+              protocol = "blackhole";
+            }
+          ];
+        };
+      };
+    };
+  };
+}