commit eea05fdd63bdddbb8cce7bf8608ad017250a62c3 Author: Melorise <0d00@0721.hk> Date: Fri Jul 24 21:31:46 2026 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..750baeb --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +result +result-* diff --git a/README.md b/README.md new file mode 100644 index 0000000..9010e1d --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# Nix 打包说明 + +本目录把锁定版本的上游桌面项目从源码构建为 Linux Nix Flake,不复用上游预制安装包。界面代码、原生 Node 模块和最终 ASAR 均在 Nix 构建中生成;共享 Electron、稳定运行组件及部分随包资源来自 nixpkgs 或 Flake inputs。版本和来源由 `flake.lock`、pnpm 锁文件及 Cargo 锁文件记录。 + +## 补丁说明 + +补丁按 `nix/unwrapped.nix` 中的顺序应用。 + +### 标识调整 + +上游构建配置中的可执行文件名、桌面项和图标标识与本地包名不同。补丁统一这些标识,避免生成物保留不匹配的窗口类、图标名和可执行文件元数据。 + +### 禁用内置更新 + +Nix store 只读,运行中的程序不能可靠替换自身或随包运行组件;绕过 Flake 锁定下载更新还会产生版本混用。补丁因此在 Linux 上跳过应用更新检查,并拒绝运行组件的内置升级请求,更新统一通过重新构建 Flake 完成。 + +### 固定稳定运行组件 + +当前打包只提供 nixpkgs 中的稳定版本,没有随包放入其他发布通道的文件。补丁让 Linux 路径解析始终选择已提供的稳定文件,防止旧配置或界面状态指向不存在的替代文件。 + +### NixOS 权限包装 + +上游会检查 setuid 所有权,并尝试通过 `pkexec` 对程序文件执行 `chown` 和 `chmod`;这些操作不适用于只读 Nix store。NixOS 模块改为在 `/run/wrappers` 创建 capability 包装器。补丁在需要额外权限时选择该包装器,并把它的存在视为权限已经配置;未启用模块时则给出明确错误,而不尝试修改 store。 + +### 共享 Electron 兼容 + +Nix 启动器使用共享 Electron,并把 `app.asar` 作为参数传入。此方式下 Electron 将进程视为 default app,`app.isPackaged` 为 `false`;而工具库把 `is.dev` 定义为 `!app.isPackaged`。若直接沿用该判断,正式构建会误走开发分支,自动打开 DevTools、选择开发资源目录,并影响静默启动和窗口页面加载。 + +补丁把“工具库判断为开发环境且应用路径不是 ASAR”作为实际开发状态。另一个差异是共享 Electron 的 `process.resourcesPath` 指向 Electron 自身的 store 路径,而不是应用资源目录;补丁改从 `app.getAppPath()` 所指 ASAR 的父目录定位随包文件和原生模块。主窗口、悬浮窗口、资源复制和原生模块加载共用这一约定,删除其中任一处修改都可能重新造成路径不一致。 + +## NixOS 模块 + +模块负责安装软件包和创建上述权限包装器,并提供可选的图形会话自启动及额外 Electron 参数。它不创建常驻系统服务。 + +默认构建: + +```sh +nix build +``` diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..171cac7 --- /dev/null +++ b/flake.lock @@ -0,0 +1,128 @@ +{ + "nodes": { + "asn": { + "flake": false, + "locked": { + "narHash": "sha256-bYBRUboYCyEixK/jdhPayyKdB0HQegLmiFXvzB7ZFDU=", + "type": "file", + "url": "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb" + }, + "original": { + "type": "file", + "url": "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb" + } + }, + "clash-party": { + "flake": false, + "locked": { + "lastModified": 1783321592, + "narHash": "sha256-ituoVv/q/aHt3KOt4fHRDGHGEwnF/nb2E73s/oYw74U=", + "owner": "mihomo-party-org", + "repo": "clash-party", + "rev": "1caff4cabd83d0e2768ecadf52c85c5dcc0ed9de", + "type": "github" + }, + "original": { + "owner": "mihomo-party-org", + "ref": "v2.0.0", + "repo": "clash-party", + "type": "github" + } + }, + "country": { + "flake": false, + "locked": { + "narHash": "sha256-DpM+/Jsyy7R51/AQKDBVhuT8Fgr1crpz0uaX/0xV6bs=", + "type": "file", + "url": "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country-lite.mmdb" + }, + "original": { + "type": "file", + "url": "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country-lite.mmdb" + } + }, + "geoip": { + "flake": false, + "locked": { + "narHash": "sha256-ov9zEg2tBCpxru5uNzMs6Npaawz0CXthUaBDQIBcOqs=", + "type": "file", + "url": "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat" + }, + "original": { + "type": "file", + "url": "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat" + } + }, + "geosite": { + "flake": false, + "locked": { + "narHash": "sha256-PAA0zUq0to9HpJ7+sXXcCm6GgvuZvci5HVoH/u41m0k=", + "type": "file", + "url": "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat" + }, + "original": { + "type": "file", + "url": "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat" + } + }, + "metadb": { + "flake": false, + "locked": { + "narHash": "sha256-UVm3qjQmgmDSzv6zXPgVXfEQsHwDzl3yTWrRXzh9hrs=", + "type": "file", + "url": "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb" + }, + "original": { + "type": "file", + "url": "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1784796856, + "narHash": "sha256-wWFrV5/Qbm+lyt5x20E/bSbfJiGKMo4RCxZV8cl/WZI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e2587caef70cea85dd97d7daab492899902dbf5d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "asn": "asn", + "clash-party": "clash-party", + "country": "country", + "geoip": "geoip", + "geosite": "geosite", + "metadb": "metadb", + "nixpkgs": "nixpkgs", + "sysproxy": "sysproxy" + } + }, + "sysproxy": { + "flake": false, + "locked": { + "lastModified": 1781896006, + "narHash": "sha256-z/IlR6kQfzFkg42fmKKTUTFGgcMLobcNZHeHQFDN5kw=", + "owner": "mihomo-party-org", + "repo": "sysproxy-rs-opti", + "rev": "d6fbb3dee696739e7d32ece15479a7770f687c9b", + "type": "github" + }, + "original": { + "owner": "mihomo-party-org", + "ref": "main", + "repo": "sysproxy-rs-opti", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..963fb91 --- /dev/null +++ b/flake.nix @@ -0,0 +1,103 @@ +{ + description = "Clash Party, built from source with a reproducible Nix flake"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + clash-party = { + url = "github:mihomo-party-org/clash-party/v2.0.0"; + flake = false; + }; + + sysproxy = { + url = "github:mihomo-party-org/sysproxy-rs-opti/main"; + flake = false; + }; + + country = { + url = "file+https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country-lite.mmdb"; + flake = false; + }; + + geoip = { + url = "file+https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat"; + flake = false; + }; + + geosite = { + url = "file+https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat"; + flake = false; + }; + + metadb = { + url = "file+https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb"; + flake = false; + }; + + asn = { + url = "file+https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb"; + flake = false; + }; + }; + + outputs = + inputs@{ self, nixpkgs, ... }: + let + systems = [ + "x86_64-linux" + "aarch64-linux" + ]; + forEachSystem = nixpkgs.lib.genAttrs systems; + mkPkgs = + system: + import nixpkgs { + inherit system; + config.allowUnfree = true; + }; + mkPackage = + system: + let + pkgs = mkPkgs system; + in + pkgs.callPackage ./nix/package.nix { + src = inputs.clash-party; + sysproxySrc = inputs.sysproxy; + inherit (inputs) + country + geoip + geosite + metadb + asn + ; + subStore = pkgs.sub-store; + subStoreFrontend = pkgs.sub-store-frontend; + notoFontsColorEmoji = pkgs.noto-fonts-color-emoji; + }; + in + { + packages = forEachSystem ( + system: + let + package = mkPackage system; + in + { + clash-party = package; + default = package; + inherit (package) sysproxy unwrapped; + } + ); + + apps = forEachSystem (system: { + clash-party = { + type = "app"; + program = "${self.packages.${system}.clash-party}/bin/clash-party"; + }; + default = self.apps.${system}.clash-party; + }); + + nixosModules = { + clash-party = import ./nix/module.nix { inherit self; }; + default = self.nixosModules.clash-party; + }; + }; +} diff --git a/nix/module.nix b/nix/module.nix new file mode 100644 index 0000000..603f3d7 --- /dev/null +++ b/nix/module.nix @@ -0,0 +1,55 @@ +{ self }: +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.clash-party; + system = pkgs.stdenv.hostPlatform.system; + package = cfg.package.override { + commandLineArgs = cfg.extraArgs; + }; +in +{ + options.programs.clash-party = { + enable = lib.mkEnableOption "Clash Party"; + + package = lib.mkOption { + type = lib.types.package; + default = self.packages.${system}.clash-party; + defaultText = lib.literalExpression "clash-party-flake.packages.${system}.clash-party"; + description = "Clash Party package to install."; + }; + + autoStart = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Start Clash Party automatically in graphical sessions."; + }; + + extraArgs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "--ozone-platform-hint=auto" ]; + description = "Additional command-line arguments passed to Electron."; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ package ]; + + security.wrappers.clash-party-mihomo = { + source = lib.getExe package.mihomo; + owner = "root"; + group = "root"; + capabilities = "cap_net_admin,cap_net_raw,cap_net_bind_service=+ep"; + }; + + environment.etc."xdg/autostart/clash-party.desktop" = lib.mkIf cfg.autoStart { + source = "${package}/share/applications/clash-party.desktop"; + }; + }; +} diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 0000000..de16c04 --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,132 @@ +{ + lib, + callPackage, + stdenv, + electron_41, + makeWrapper, + copyDesktopItems, + makeDesktopItem, + wrapGAppsHook3, + autoPatchelfHook, + alsa-lib, + at-spi2-core, + glib, + libGL, + libayatana-appindicator, + libsecret, + libnotify, + libXScrnSaver, + libXtst, + nspr, + nss, + openssl, + udev, + src, + sysproxySrc, + country, + geoip, + geosite, + metadb, + asn, + subStore, + subStoreFrontend, + notoFontsColorEmoji, + mihomo, + commandLineArgs ? [ ], +}: + +let + sysproxy = callPackage ./sysproxy.nix { inherit sysproxySrc; }; + + unwrapped = callPackage ./unwrapped.nix { + inherit + src + sysproxy + country + geoip + geosite + metadb + asn + subStore + subStoreFrontend + notoFontsColorEmoji + mihomo + ; + }; +in +stdenv.mkDerivation (finalAttrs: { + pname = "clash-party"; + version = "2.0.0"; + + dontUnpack = true; + + nativeBuildInputs = [ + autoPatchelfHook + copyDesktopItems + makeWrapper + wrapGAppsHook3 + ]; + + buildInputs = [ + alsa-lib + at-spi2-core + glib + libGL + libayatana-appindicator + libnotify + libsecret + libXScrnSaver + libXtst + nspr + nss + openssl + stdenv.cc.cc.lib + udev + ]; + + installPhase = '' + runHook preInstall + + mkdir -p $out/lib/clash-party + cp -r ${unwrapped}/lib/clash-party/resources $out/lib/clash-party/ + cp -r ${unwrapped}/share $out/ + chmod -R u+w $out/share + + makeWrapper ${lib.getExe electron_41} $out/bin/clash-party \ + --inherit-argv0 \ + --add-flags "$out/lib/clash-party/resources/app.asar" \ + --add-flags ${lib.escapeShellArg (lib.escapeShellArgs commandLineArgs)} \ + --set-default ELECTRON_OZONE_PLATFORM_HINT auto + + runHook postInstall + ''; + + desktopItems = [ + (makeDesktopItem { + name = "clash-party"; + desktopName = "Clash Party"; + genericName = "Proxy Client"; + comment = "A GUI client based on Mihomo"; + exec = "clash-party %U"; + icon = "clash-party"; + startupWMClass = "clash-party"; + categories = [ "Utility" ]; + mimeTypes = [ + "x-scheme-handler/clash" + "x-scheme-handler/mihomo" + ]; + }) + ]; + + passthru = { + inherit mihomo sysproxy unwrapped; + }; + + meta = { + description = "Clash Party desktop client built from source"; + homepage = "https://github.com/mihomo-party-org/clash-party"; + license = lib.licenses.gpl3Only; + mainProgram = "clash-party"; + platforms = lib.platforms.linux; + }; +}) diff --git a/nix/sysproxy-Cargo.lock b/nix/sysproxy-Cargo.lock new file mode 100644 index 0000000..84c15be --- /dev/null +++ b/nix/sysproxy-Cargo.lock @@ -0,0 +1,1155 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-executor" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "iptools" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d423b27a03e1931bcf0a52bc6c554f0b48573f2f51688c92ac6cddf08494943c" +dependencies = [ + "js-sys", + "lazy-regex", + "once_cell", + "tinyvec", + "wasm-bindgen", +] + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy-regex" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bae91019476d3ec7147de9aa291cadb6d870abf2f3015d2da73a90325ac1496" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de9c1e1439d8b7b3061b2d209809f447ca33241733d9a3c01eabf2dc8d94358" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.119", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "napi" +version = "2.16.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3" +dependencies = [ + "bitflags", + "ctor", + "napi-derive", + "napi-sys", + "once_cell", +] + +[[package]] +name = "napi-build" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b4532cf86bfef556348ac65e561e3123879f0e7566cca6d43a6ff5326f13df" + +[[package]] +name = "napi-derive" +version = "2.16.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" +dependencies = [ + "cfg-if", + "convert_case", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "napi-derive-backend" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" +dependencies = [ + "convert_case", + "once_cell", + "proc-macro2", + "quote", + "regex", + "semver", + "syn 2.0.119", +] + +[[package]] +name = "napi-sys" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" +dependencies = [ + "libloading", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serial_test" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d" +dependencies = [ + "futures-executor", + "futures-util", + "log", + "once_cell", + "parking_lot", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sysproxy" +version = "0.5.1" +dependencies = [ + "criterion", + "iptools", + "log", + "napi", + "napi-build", + "napi-derive", + "serial_test", + "system-configuration", + "thiserror", + "tokio", + "url", + "windows", + "winreg", + "xdg", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "url" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "xdg" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5" + +[[package]] +name = "zerocopy" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + diff --git a/nix/sysproxy.nix b/nix/sysproxy.nix new file mode 100644 index 0000000..2367f6f --- /dev/null +++ b/nix/sysproxy.nix @@ -0,0 +1,60 @@ +{ + lib, + rustPlatform, + fetchPnpmDeps, + nodejs_22, + pnpm_10, + pnpmConfigHook, + sysproxySrc, +}: + +rustPlatform.buildRustPackage (finalAttrs: { + pname = "clash-party-sysproxy"; + version = "0.5.1"; + src = sysproxySrc; + + cargoLock.lockFile = ./sysproxy-Cargo.lock; + + pnpmDeps = fetchPnpmDeps { + pname = finalAttrs.pname; + version = finalAttrs.version; + src = sysproxySrc; + pnpm = pnpm_10; + fetcherVersion = 4; + prePnpmInstall = '' + pnpm config set fetch-retries 5 + pnpm config set fetch-timeout 120000 + pnpm config set network-concurrency 4 + ''; + hash = "sha256-n/unBCkpSSShYliEeeWOAjM7r7ZUdfROBYyqzw8JLB0="; + }; + + nativeBuildInputs = [ + nodejs_22 + pnpmConfigHook + pnpm_10 + ]; + + postPatch = '' + rm -f .cargo/config.toml + cp ${./sysproxy-Cargo.lock} Cargo.lock + chmod u+w Cargo.lock + ''; + + buildPhase = '' + runHook preBuild + pnpm exec napi build --platform --release + runHook postBuild + ''; + + # N-API symbols are supplied by Node/Electron when the module is loaded, so + # Cargo's standalone test-link step cannot resolve them. + doCheck = false; + + installPhase = '' + runHook preInstall + mkdir -p $out + cp sysproxy.*.node $out/ + runHook postInstall + ''; +}) diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix new file mode 100644 index 0000000..35fc227 --- /dev/null +++ b/nix/unwrapped.nix @@ -0,0 +1,139 @@ +{ + lib, + stdenv, + autoPatchelfHook, + electron_41, + fetchPnpmDeps, + nodejs_22, + pnpm_10, + pnpmConfigHook, + alsa-lib, + at-spi2-core, + glib, + libGL, + libayatana-appindicator, + libnotify, + libsecret, + libXScrnSaver, + libXtst, + nspr, + nss, + openssl, + pkg-config, + udev, + src, + sysproxy, + country, + geoip, + geosite, + metadb, + asn, + subStore, + subStoreFrontend, + notoFontsColorEmoji, + mihomo, +}: + +stdenv.mkDerivation (finalAttrs: { + pname = "clash-party-unwrapped"; + version = "2.0.0"; + inherit src; + + patches = [ + ../patches/branding.patch + ../patches/disable-updater.patch + ../patches/stable-core-only.patch + ../patches/nix-tun-wrapper.patch + ../patches/system-electron-resources.patch + ]; + + pnpmDeps = fetchPnpmDeps { + pname = finalAttrs.pname; + version = finalAttrs.version; + inherit src; + pnpm = pnpm_10; + fetcherVersion = 4; + prePnpmInstall = '' + pnpm config set fetch-retries 5 + pnpm config set fetch-timeout 120000 + pnpm config set network-concurrency 4 + ''; + hash = "sha256-plsb9TjmprG5k2kYkqItEunTeyJWopRBH0SzzU/qM/8="; + }; + + nativeBuildInputs = [ + autoPatchelfHook + nodejs_22 + pkg-config + pnpmConfigHook + pnpm_10 + ]; + + buildInputs = [ + alsa-lib + at-spi2-core + glib + libGL + libayatana-appindicator + libnotify + libsecret + libXScrnSaver + libXtst + nspr + nss + openssl + stdenv.cc.cc.lib + udev + ]; + + env = { + ELECTRON_SKIP_BINARY_DOWNLOAD = "1"; + npm_config_build_from_source = "true"; + }; + + postPatch = '' + mkdir -p extra/sidecar extra/files + ln -s ${mihomo}/bin/mihomo extra/sidecar/mihomo + cp ${sysproxy}/sysproxy.linux-${if stdenv.hostPlatform.isx86_64 then "x64" else "arm64"}-gnu.node \ + extra/sidecar/ + + install -Dm444 ${country} extra/files/country.mmdb + install -Dm444 ${geoip} extra/files/geoip.dat + install -Dm444 ${geosite} extra/files/geosite.dat + install -Dm444 ${metadb} extra/files/geoip.metadb + install -Dm444 ${asn} extra/files/ASN.mmdb + install -Dm444 ${subStore}/share/sub-store/sub-store.bundle.js \ + extra/files/sub-store.bundle.cjs + cp -r ${subStoreFrontend} extra/files/sub-store-frontend + find ${notoFontsColorEmoji} -name NotoColorEmoji.ttf -exec \ + install -Dm444 {} src/renderer/src/assets/NotoColorEmoji.ttf \; + ''; + + buildPhase = '' + runHook preBuild + + mkdir .electron-dist + cp -RL ${electron_41.dist}/. .electron-dist/ + chmod -R u+w .electron-dist + + node_modules/.bin/electron-vite build + npm_config_nodedir=${electron_41.headers} \ + node_modules/.bin/electron-builder --dir \ + --config=electron-builder.yml \ + --config.electronDist=$PWD/.electron-dist \ + --config.electronVersion=${electron_41.version} + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/lib/clash-party + cp -r dist/linux-unpacked/resources $out/lib/clash-party/ + install -Dm644 build/icon.png $out/share/icons/hicolor/512x512/apps/clash-party.png + + runHook postInstall + ''; + +}) diff --git a/patches/branding.patch b/patches/branding.patch new file mode 100644 index 0000000..afb9f52 --- /dev/null +++ b/patches/branding.patch @@ -0,0 +1,11 @@ +diff --git a/electron-builder.yml b/electron-builder.yml +--- a/electron-builder.yml ++++ b/electron-builder.yml +@@ -59 +59 @@ +- executableName: mihomo-party ++ executableName: clash-party +@@ -68,2 +68,2 @@ +- StartupWMClass: mihomo-party +- Icon: mihomo-party ++ StartupWMClass: clash-party ++ Icon: clash-party diff --git a/patches/disable-updater.patch b/patches/disable-updater.patch new file mode 100644 index 0000000..d6016dd --- /dev/null +++ b/patches/disable-updater.patch @@ -0,0 +1,24 @@ +diff --git a/src/main/resolve/autoUpdater.ts b/src/main/resolve/autoUpdater.ts +--- a/src/main/resolve/autoUpdater.ts ++++ b/src/main/resolve/autoUpdater.ts +@@ -48,4 +48,8 @@ + export async function checkUpdate(): Promise { ++ if (process.platform === 'linux') { ++ return undefined ++ } ++ + const [{ 'mixed-port': mixedPort = DEFAULT_MIHOMO_PORTS.mixed }, { githubProxy = '' }] = + await Promise.all([getControledMihomoConfig(), getAppConfig()]) + const githubUrl = +diff --git a/src/main/core/mihomoApi.ts b/src/main/core/mihomoApi.ts +--- a/src/main/core/mihomoApi.ts ++++ b/src/main/core/mihomoApi.ts +@@ -400,4 +400,8 @@ + export const mihomoUpgrade = async (): Promise => { ++ if (process.platform === 'linux') { ++ throw new Error('Mihomo is managed by Nix on Linux') ++ } ++ + const instance = await getAxios() + return await instance.post('/upgrade', undefined, { timeout: 90000 }) + } diff --git a/patches/nix-tun-wrapper.patch b/patches/nix-tun-wrapper.patch new file mode 100644 index 0000000..fc7f080 --- /dev/null +++ b/patches/nix-tun-wrapper.patch @@ -0,0 +1,54 @@ +diff --git a/src/main/core/manager.ts b/src/main/core/manager.ts +--- a/src/main/core/manager.ts ++++ b/src/main/core/manager.ts +@@ -79,3 +79,4 @@ + const execFilePromise = promisify(execFile) + const ctlParam = process.platform === 'win32' ? '-ext-ctl-pipe' : '-ext-ctl-unix' ++const nixMihomoWrapper = '/run/wrappers/bin/clash-party-mihomo' + const coreHookTimeout = 30000 +@@ -370,5 +371,8 @@ + return { +- corePath: mihomoCorePath(core), ++ corePath: ++ process.platform === 'linux' && tun?.enable && existsSync(nixMihomoWrapper) ++ ? nixMihomoWrapper ++ : mihomoCorePath(core), + workDir: diffWorkDir ? mihomoProfileWorkDir(current) : mihomoWorkDir(), + safePath: diffWorkDir ? mihomoWorkDir() : undefined, + ipcPath, +diff --git a/src/main/core/permissions.ts b/src/main/core/permissions.ts +--- a/src/main/core/permissions.ts ++++ b/src/main/core/permissions.ts +@@ -14,5 +14,6 @@ + const execPromise = promisify(exec) + const execFilePromise = promisify(execFile) ++const nixMihomoWrapper = '/run/wrappers/bin/clash-party-mihomo' + + // 内核名称白名单 + const ALLOWED_CORES = ['mihomo', 'mihomo-alpha', 'mihomo-smart'] as const +@@ -76,9 +77,13 @@ + try { + if (process.platform === 'win32') { + return await checkAdminPrivileges() + } + ++ if (process.platform === 'linux' && existsSync(nixMihomoWrapper)) { ++ return true ++ } ++ + if (process.platform === 'darwin' || process.platform === 'linux') { + const stats = await stat(corePath) + return (stats.mode & 0o4000) !== 0 && stats.uid === 0 + } +@@ -253,4 +258,9 @@ + if (process.platform === 'linux') { +- await execFilePromise('pkexec', ['chown', 'root:root', corePath]) +- await execFilePromise('pkexec', ['chmod', '+sx', corePath]) ++ if (existsSync(nixMihomoWrapper)) { ++ return ++ } ++ ++ throw new Error( ++ 'TUN support requires enabling programs.clash-party in the NixOS configuration' ++ ) + } diff --git a/patches/stable-core-only.patch b/patches/stable-core-only.patch new file mode 100644 index 0000000..2a12667 --- /dev/null +++ b/patches/stable-core-only.patch @@ -0,0 +1,12 @@ +diff --git a/src/main/utils/dirs.ts b/src/main/utils/dirs.ts +--- a/src/main/utils/dirs.ts ++++ b/src/main/utils/dirs.ts +@@ -79,6 +79,10 @@ + export function mihomoCorePath(core: string): string { ++ if (process.platform === 'linux') { ++ core = 'mihomo' ++ } ++ + const isWin = process.platform === 'win32' + // 处理 Smart 内核 + if (core === 'mihomo-smart') { diff --git a/patches/system-electron-resources.patch b/patches/system-electron-resources.patch new file mode 100644 index 0000000..09879cb --- /dev/null +++ b/patches/system-electron-resources.patch @@ -0,0 +1,107 @@ +diff --git a/src/native/sysproxy/index.js b/src/native/sysproxy/index.js +--- a/src/native/sysproxy/index.js ++++ b/src/native/sysproxy/index.js +@@ -70,6 +70,12 @@ function getResourcesPath() { + try { + const { app } = require('electron') + const appPath = app.getAppPath() ++ // A shared system Electron keeps process.resourcesPath in Electron's own ++ // store path. The directory containing app.asar is the app resource path. ++ if (appPath.endsWith('.asar') && existsSync(join(dirname(appPath), 'sidecar'))) { ++ return dirname(appPath) ++ } ++ + if (existsSync(join(appPath, 'extra', 'sidecar'))) { + return appPath + } +diff --git a/src/main/utils/dirs.ts b/src/main/utils/dirs.ts +--- a/src/main/utils/dirs.ts ++++ b/src/main/utils/dirs.ts +@@ -62,13 +62,16 @@ export function workDir(): string { + } + ++export function isDevelopment(): boolean { ++ return is.dev && !app.getAppPath().endsWith('asar') ++} ++ + export function resourcesDir(): string { +- if (is.dev) { ++ const appPath = app.getAppPath() ++ if (appPath.endsWith('asar')) { ++ return path.dirname(appPath) ++ } else if (isDevelopment()) { + return path.join(__dirname, '../../extra') + } else { +- if (app.getAppPath().endsWith('asar')) { +- return process.resourcesPath +- } else { +- return path.join(app.getAppPath(), 'resources') +- } ++ return path.join(appPath, 'resources') + } + } +diff --git a/src/main/window.ts b/src/main/window.ts +--- a/src/main/window.ts ++++ b/src/main/window.ts +@@ -1,11 +1,10 @@ + import { join } from 'path' + import { readFileSync, writeFileSync } from 'fs' + import { BrowserWindow, Menu, screen, shell } from 'electron' +-import { is } from '@electron-toolkit/utils' + import icon from '../../resources/icon.png?asset' + import { getAppConfig } from './config' + import { quitWithoutCore, stopCore } from './core/manager' + import { triggerSysProxy } from './sys/sysproxy' + import { hideDockIcon, showDockIcon } from './resolve/tray' +-import { dataDir } from './utils/dirs' ++import { dataDir, isDevelopment } from './utils/dirs' + import { mainWindowLogger } from './utils/logger' +@@ -172,10 +171,10 @@ export async function createMainWindow(silentStart = false): Promise { + }) + +- if (is.dev) { ++ if (isDevelopment()) { + mainWindow.webContents.openDevTools() + } + +- if (is.dev && process.env['ELECTRON_RENDERER_URL']) { ++ if (isDevelopment() && process.env['ELECTRON_RENDERER_URL']) { + mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) + } else { + mainWindow.loadFile(join(__dirname, '../renderer/index.html')) +@@ -199,7 +198,7 @@ function setupWindowEvents(window: BrowserWindow, config: WindowConfig): void { + } + + // 开发模式下始终显示窗口 +- if (!silentStart || is.dev) { ++ if (!silentStart || isDevelopment()) { + clearQuitTimeout() + window.show() + window.focusOnWebView() +diff --git a/src/main/resolve/floatingWindow.ts b/src/main/resolve/floatingWindow.ts +--- a/src/main/resolve/floatingWindow.ts ++++ b/src/main/resolve/floatingWindow.ts +@@ -1,8 +1,8 @@ + import { join } from 'path' +-import { is } from '@electron-toolkit/utils' + import { BrowserWindow, ipcMain } from 'electron' + import windowStateKeeper from 'electron-window-state' + import { getAppConfig, patchAppConfig } from '../config' ++import { isDevelopment } from '../utils/dirs' + import { floatingWindowLogger } from '../utils/logger' + import { applyTheme } from './theme' + import { buildContextMenu, showTrayIcon } from './tray' +@@ -86,11 +86,11 @@ async function createFloatingWindow(): Promise { + + // 加载页面 + const url = +- is.dev && process.env['ELECTRON_RENDERER_URL'] ++ isDevelopment() && process.env['ELECTRON_RENDERER_URL'] + ? `${process.env['ELECTRON_RENDERER_URL']}/floating.html` + : join(__dirname, '../renderer/floating.html') + +- is.dev ? await floatingWindow.loadURL(url) : await floatingWindow.loadFile(url) ++ isDevelopment() ? await floatingWindow.loadURL(url) : await floatingWindow.loadFile(url) + } catch (error) { + logError('Failed to create floating window', error) + floatingWindow = null