mirror of
https://github.com/Melorise/cp-nix.git
synced 2026-11-07 14:39:34 +00:00
init
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
result
|
||||||
|
result-*
|
||||||
@@ -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
|
||||||
|
```
|
||||||
Generated
+128
@@ -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
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
+132
@@ -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;
|
||||||
|
};
|
||||||
|
})
|
||||||
Generated
+1155
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
|
'';
|
||||||
|
})
|
||||||
@@ -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
|
||||||
|
'';
|
||||||
|
|
||||||
|
})
|
||||||
@@ -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
|
||||||
@@ -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<IAppVersion | undefined> {
|
||||||
|
+ 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<void> => {
|
||||||
|
+ 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 })
|
||||||
|
}
|
||||||
@@ -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'
|
||||||
|
+ )
|
||||||
|
}
|
||||||
@@ -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') {
|
||||||
@@ -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<void> {
|
||||||
|
})
|
||||||
|
|
||||||
|
- 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<void> {
|
||||||
|
|
||||||
|
// 加载页面
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user