From 44be5d20696b8653e7d80e84c050a7ba0ffed1d3 Mon Sep 17 00:00:00 2001 From: sudoup Date: Wed, 16 Sep 2026 22:01:48 +0300 Subject: [PATCH 1/6] support disable-icmp-forwarding in tun options Add a "Disable ICMP Forwarding" VpnService option and pass it through to the core tun configuration. When enabled (default), ICMP echo requests are answered locally instead of being forwarded directly, avoiding ICMP leaks and the 'receive ICMP echo reply: i/o timeout' error. --- core/src/main/cpp/main.c | 3 ++- core/src/main/golang/native/tun.go | 4 ++-- core/src/main/golang/native/tun/tun.go | 23 ++++++++++--------- .../java/com/github/kr328/clash/core/Clash.kt | 3 ++- .../github/kr328/clash/core/bridge/Bridge.kt | 10 +++++++- .../clash/design/NetworkSettingsDesign.kt | 7 ++++++ design/src/main/res/values/strings.xml | 2 ++ .../github/kr328/clash/service/TunService.kt | 1 + .../clash/service/clash/module/TunModule.kt | 2 ++ .../kr328/clash/service/store/ServiceStore.kt | 5 ++++ 10 files changed, 44 insertions(+), 16 deletions(-) diff --git a/core/src/main/cpp/main.c b/core/src/main/cpp/main.c index 61e431c8a1..4b7aa825b3 100644 --- a/core/src/main/cpp/main.c +++ b/core/src/main/cpp/main.c @@ -116,6 +116,7 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeStartTun(JNIEnv *env, jobje jstring gateway, jstring portal, jstring dns, + jboolean disable_icmp_forwarding, jobject cb) { TRACE_METHOD(); @@ -125,7 +126,7 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeStartTun(JNIEnv *env, jobje scoped_string _dns = get_string(dns); jobject _interface = new_global(cb); - startTun(fd, _stack, _gateway, _portal, _dns, _interface); + startTun(fd, _stack, _gateway, _portal, _dns, (int) disable_icmp_forwarding, _interface); } JNIEXPORT void JNICALL diff --git a/core/src/main/golang/native/tun.go b/core/src/main/golang/native/tun.go index 860a1fdc21..2525ff10f6 100644 --- a/core/src/main/golang/native/tun.go +++ b/core/src/main/golang/native/tun.go @@ -64,7 +64,7 @@ func (t *remoteTun) close() { } //export startTun -func startTun(fd C.int, stack, gateway, portal, dns C.c_string, callback unsafe.Pointer) C.int { +func startTun(fd C.int, stack, gateway, portal, dns C.c_string, disableICMPForwarding C.int, callback unsafe.Pointer) C.int { rTunLock.Lock() defer rTunLock.Unlock() @@ -83,7 +83,7 @@ func startTun(fd C.int, stack, gateway, portal, dns C.c_string, callback unsafe. app.ApplyTunContext(remote.markSocket, remote.querySocketUid) - closer, err := tun.Start(f, s, g, p, d) + closer, err := tun.Start(f, s, g, p, d, disableICMPForwarding != 0) if err != nil { remote.close() diff --git a/core/src/main/golang/native/tun/tun.go b/core/src/main/golang/native/tun/tun.go index ee2c2a9124..87991ad72c 100644 --- a/core/src/main/golang/native/tun/tun.go +++ b/core/src/main/golang/native/tun/tun.go @@ -14,7 +14,7 @@ import ( "github.com/metacubex/mihomo/tunnel" ) -func Start(fd int, stack, gateway, portal, dns string) (io.Closer, error) { +func Start(fd int, stack, gateway, portal, dns string, disableICMPForwarding bool) (io.Closer, error) { log.Debugln("TUN: fd = %d, stack = %s, gateway = %s, portal = %s, dns = %s", fd, stack, gateway, portal, dns) tunStack, ok := C.StackTypeMapping[strings.ToLower(stack)] @@ -52,16 +52,17 @@ func Start(fd int, stack, gateway, portal, dns string) (io.Closer, error) { } options := LC.Tun{ - Enable: true, - Device: sing_tun.InterfaceName, - Stack: tunStack, - DNSHijack: dnsHijack, - AutoRoute: false, // had set route in TunService.kt - AutoDetectInterface: false, // implements by VpnService::protect - Inet4Address: prefix4, - Inet6Address: prefix6, - MTU: 9000, // private const val TUN_MTU = 9000 in TunService.kt - FileDescriptor: fd, + Enable: true, + Device: sing_tun.InterfaceName, + Stack: tunStack, + DNSHijack: dnsHijack, + AutoRoute: false, // had set route in TunService.kt + AutoDetectInterface: false, // implements by VpnService::protect + Inet4Address: prefix4, + Inet6Address: prefix6, + MTU: 9000, // private const val TUN_MTU = 9000 in TunService.kt + FileDescriptor: fd, + DisableICMPForwarding: disableICMPForwarding, } tunOptions, _ := json.Marshal(options) diff --git a/core/src/main/java/com/github/kr328/clash/core/Clash.kt b/core/src/main/java/com/github/kr328/clash/core/Clash.kt index edba62bed0..6038b57e1b 100644 --- a/core/src/main/java/com/github/kr328/clash/core/Clash.kt +++ b/core/src/main/java/com/github/kr328/clash/core/Clash.kt @@ -70,10 +70,11 @@ object Clash { gateway: String, portal: String, dns: String, + disableIcmpForwarding: Boolean, markSocket: (Int) -> Boolean, querySocketUid: (protocol: Int, source: InetSocketAddress, target: InetSocketAddress) -> Int ) { - Bridge.nativeStartTun(fd, stack, gateway, portal, dns, object : TunInterface { + Bridge.nativeStartTun(fd, stack, gateway, portal, dns, disableIcmpForwarding, object : TunInterface { override fun markSocket(fd: Int) { markSocket(fd) } diff --git a/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt b/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt index fa3b9247ef..1dd12ce901 100644 --- a/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt +++ b/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt @@ -19,7 +19,15 @@ object Bridge { external fun nativeNotifyDnsChanged(dnsList: String) external fun nativeNotifyTimeZoneChanged(name: String, offset: Int) external fun nativeNotifyInstalledAppChanged(uidList: String) - external fun nativeStartTun(fd: Int, stack: String, gateway: String, portal: String, dns: String, cb: TunInterface) + external fun nativeStartTun( + fd: Int, + stack: String, + gateway: String, + portal: String, + dns: String, + disableIcmpForwarding: Boolean, + cb: TunInterface + ) external fun nativeStopTun() external fun nativeStartHttp(listenAt: String): String? external fun nativeStopHttp() diff --git a/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt b/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt index 93e1ee3d07..2f5178838d 100644 --- a/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt +++ b/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt @@ -70,6 +70,13 @@ class NetworkSettingsDesign( configure = vpnDependencies::add, ) + switch( + value = srvStore::disableIcmpForwarding, + title = R.string.disable_icmp_forwarding, + summary = R.string.disable_icmp_forwarding_summary, + configure = vpnDependencies::add, + ) + switch( value = srvStore::allowBypass, title = R.string.allow_bypass, diff --git a/design/src/main/res/values/strings.xml b/design/src/main/res/values/strings.xml index 4e43d3fd26..1aabf02a20 100644 --- a/design/src/main/res/values/strings.xml +++ b/design/src/main/res/values/strings.xml @@ -135,6 +135,8 @@ Bypass private network addresses DNS Hijacking Handle all dns packet + Disable ICMP Forwarding + Reply ICMP echo requests locally instead of forwarding them Block Loopback Block loopback connections Allow Bypass diff --git a/service/src/main/java/com/github/kr328/clash/service/TunService.kt b/service/src/main/java/com/github/kr328/clash/service/TunService.kt index 8550f8ab81..9cf78729fa 100644 --- a/service/src/main/java/com/github/kr328/clash/service/TunService.kt +++ b/service/src/main/java/com/github/kr328/clash/service/TunService.kt @@ -222,6 +222,7 @@ class TunService : VpnService(), CoroutineScope by CoroutineScope(Dispatchers.De gateway = "$TUN_GATEWAY/$TUN_SUBNET_PREFIX" + if (store.allowIpv6) ",$TUN_GATEWAY6/$TUN_SUBNET_PREFIX6" else "", portal = TUN_PORTAL + if (store.allowIpv6) ",$TUN_PORTAL6" else "", dns = if (store.dnsHijacking) NET_ANY else (TUN_DNS + if (store.allowIpv6) ",$TUN_DNS6" else ""), + disableIcmpForwarding = store.disableIcmpForwarding, ) } diff --git a/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt b/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt index 84197d325f..c9237d3563 100644 --- a/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt +++ b/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt @@ -19,6 +19,7 @@ class TunModule(private val vpn: VpnService) : Module(vpn) { val gateway: String, val portal: String, val dns: String, + val disableIcmpForwarding: Boolean, ) private val connectivity = service.getSystemService()!! @@ -61,6 +62,7 @@ class TunModule(private val vpn: VpnService) : Module(vpn) { gateway = device.gateway, portal = device.portal, dns = device.dns, + disableIcmpForwarding = device.disableIcmpForwarding, markSocket = vpn::protect, querySocketUid = this::queryUid ) diff --git a/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt b/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt index d361848ff5..562123b250 100644 --- a/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt +++ b/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt @@ -61,6 +61,11 @@ class ServiceStore(context: Context) { defaultValue = "system" ) + var disableIcmpForwarding by store.boolean( + key = "disable_icmp_forwarding", + defaultValue = true + ) + var dynamicNotification by store.boolean( key = "dynamic_notification", defaultValue = true From a512ddfe7de53c72c5740e2f7121f8e05ce15b5d Mon Sep 17 00:00:00 2001 From: sudoup Date: Wed, 16 Sep 2026 22:13:16 +0300 Subject: [PATCH 2/6] support icmp-timeout in tun options Add an ICMP Timeout VpnService option and pass it through to the core tun configuration, so a custom icmp-timeout is no longer ignored. The default value 0 keeps the core default (10s). --- core/src/main/cpp/main.c | 3 ++- core/src/main/golang/native/tun.go | 4 ++-- core/src/main/golang/native/tun/tun.go | 3 ++- .../src/main/java/com/github/kr328/clash/core/Clash.kt | 3 ++- .../java/com/github/kr328/clash/core/bridge/Bridge.kt | 1 + .../github/kr328/clash/design/NetworkSettingsDesign.kt | 8 ++++++++ .../com/github/kr328/clash/design/preference/Value.kt | 10 ++++++++++ design/src/main/res/values/strings.xml | 2 ++ .../java/com/github/kr328/clash/service/TunService.kt | 1 + .../kr328/clash/service/clash/module/TunModule.kt | 2 ++ .../github/kr328/clash/service/store/ServiceStore.kt | 5 +++++ 11 files changed, 37 insertions(+), 5 deletions(-) diff --git a/core/src/main/cpp/main.c b/core/src/main/cpp/main.c index 4b7aa825b3..f1e4d85c81 100644 --- a/core/src/main/cpp/main.c +++ b/core/src/main/cpp/main.c @@ -117,6 +117,7 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeStartTun(JNIEnv *env, jobje jstring portal, jstring dns, jboolean disable_icmp_forwarding, + jint icmp_timeout, jobject cb) { TRACE_METHOD(); @@ -126,7 +127,7 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeStartTun(JNIEnv *env, jobje scoped_string _dns = get_string(dns); jobject _interface = new_global(cb); - startTun(fd, _stack, _gateway, _portal, _dns, (int) disable_icmp_forwarding, _interface); + startTun(fd, _stack, _gateway, _portal, _dns, (int) disable_icmp_forwarding, (int) icmp_timeout, _interface); } JNIEXPORT void JNICALL diff --git a/core/src/main/golang/native/tun.go b/core/src/main/golang/native/tun.go index 2525ff10f6..36f9863170 100644 --- a/core/src/main/golang/native/tun.go +++ b/core/src/main/golang/native/tun.go @@ -64,7 +64,7 @@ func (t *remoteTun) close() { } //export startTun -func startTun(fd C.int, stack, gateway, portal, dns C.c_string, disableICMPForwarding C.int, callback unsafe.Pointer) C.int { +func startTun(fd C.int, stack, gateway, portal, dns C.c_string, disableICMPForwarding C.int, icmpTimeout C.int, callback unsafe.Pointer) C.int { rTunLock.Lock() defer rTunLock.Unlock() @@ -83,7 +83,7 @@ func startTun(fd C.int, stack, gateway, portal, dns C.c_string, disableICMPForwa app.ApplyTunContext(remote.markSocket, remote.querySocketUid) - closer, err := tun.Start(f, s, g, p, d, disableICMPForwarding != 0) + closer, err := tun.Start(f, s, g, p, d, disableICMPForwarding != 0, int64(icmpTimeout)) if err != nil { remote.close() diff --git a/core/src/main/golang/native/tun/tun.go b/core/src/main/golang/native/tun/tun.go index 87991ad72c..aaa52f4d69 100644 --- a/core/src/main/golang/native/tun/tun.go +++ b/core/src/main/golang/native/tun/tun.go @@ -14,7 +14,7 @@ import ( "github.com/metacubex/mihomo/tunnel" ) -func Start(fd int, stack, gateway, portal, dns string, disableICMPForwarding bool) (io.Closer, error) { +func Start(fd int, stack, gateway, portal, dns string, disableICMPForwarding bool, icmpTimeout int64) (io.Closer, error) { log.Debugln("TUN: fd = %d, stack = %s, gateway = %s, portal = %s, dns = %s", fd, stack, gateway, portal, dns) tunStack, ok := C.StackTypeMapping[strings.ToLower(stack)] @@ -63,6 +63,7 @@ func Start(fd int, stack, gateway, portal, dns string, disableICMPForwarding boo MTU: 9000, // private const val TUN_MTU = 9000 in TunService.kt FileDescriptor: fd, DisableICMPForwarding: disableICMPForwarding, + ICMPTimeout: icmpTimeout, } tunOptions, _ := json.Marshal(options) diff --git a/core/src/main/java/com/github/kr328/clash/core/Clash.kt b/core/src/main/java/com/github/kr328/clash/core/Clash.kt index 6038b57e1b..a0753d1917 100644 --- a/core/src/main/java/com/github/kr328/clash/core/Clash.kt +++ b/core/src/main/java/com/github/kr328/clash/core/Clash.kt @@ -71,10 +71,11 @@ object Clash { portal: String, dns: String, disableIcmpForwarding: Boolean, + icmpTimeout: Int, markSocket: (Int) -> Boolean, querySocketUid: (protocol: Int, source: InetSocketAddress, target: InetSocketAddress) -> Int ) { - Bridge.nativeStartTun(fd, stack, gateway, portal, dns, disableIcmpForwarding, object : TunInterface { + Bridge.nativeStartTun(fd, stack, gateway, portal, dns, disableIcmpForwarding, icmpTimeout, object : TunInterface { override fun markSocket(fd: Int) { markSocket(fd) } diff --git a/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt b/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt index 1dd12ce901..1e8231849f 100644 --- a/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt +++ b/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt @@ -26,6 +26,7 @@ object Bridge { portal: String, dns: String, disableIcmpForwarding: Boolean, + icmpTimeout: Int, cb: TunInterface ) external fun nativeStopTun() diff --git a/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt b/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt index 2f5178838d..432d31927e 100644 --- a/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt +++ b/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt @@ -77,6 +77,14 @@ class NetworkSettingsDesign( configure = vpnDependencies::add, ) + editableText( + value = srvStore::icmpTimeout, + adapter = NullableTextAdapter.Int, + title = R.string.icmp_timeout, + empty = R.string.icmp_timeout_default, + configure = vpnDependencies::add, + ) + switch( value = srvStore::allowBypass, title = R.string.allow_bypass, diff --git a/design/src/main/java/com/github/kr328/clash/design/preference/Value.kt b/design/src/main/java/com/github/kr328/clash/design/preference/Value.kt index cf2dcb79fc..084d5cfaf5 100644 --- a/design/src/main/java/com/github/kr328/clash/design/preference/Value.kt +++ b/design/src/main/java/com/github/kr328/clash/design/preference/Value.kt @@ -19,6 +19,16 @@ interface NullableTextAdapter { } } + val Int = object : NullableTextAdapter { + override fun from(value: Int): String? { + return if (value > 0) value.toString() else "" + } + + override fun to(text: String?): Int { + return text?.toIntOrNull() ?: 0 + } + } + val String = object : NullableTextAdapter { override fun from(value: String?): String? { return value diff --git a/design/src/main/res/values/strings.xml b/design/src/main/res/values/strings.xml index 1aabf02a20..893dcacb81 100644 --- a/design/src/main/res/values/strings.xml +++ b/design/src/main/res/values/strings.xml @@ -137,6 +137,8 @@ Handle all dns packet Disable ICMP Forwarding Reply ICMP echo requests locally instead of forwarding them + ICMP Timeout + Default Block Loopback Block loopback connections Allow Bypass diff --git a/service/src/main/java/com/github/kr328/clash/service/TunService.kt b/service/src/main/java/com/github/kr328/clash/service/TunService.kt index 9cf78729fa..eb3a88036a 100644 --- a/service/src/main/java/com/github/kr328/clash/service/TunService.kt +++ b/service/src/main/java/com/github/kr328/clash/service/TunService.kt @@ -223,6 +223,7 @@ class TunService : VpnService(), CoroutineScope by CoroutineScope(Dispatchers.De portal = TUN_PORTAL + if (store.allowIpv6) ",$TUN_PORTAL6" else "", dns = if (store.dnsHijacking) NET_ANY else (TUN_DNS + if (store.allowIpv6) ",$TUN_DNS6" else ""), disableIcmpForwarding = store.disableIcmpForwarding, + icmpTimeout = store.icmpTimeout, ) } diff --git a/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt b/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt index c9237d3563..ddf794143e 100644 --- a/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt +++ b/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt @@ -20,6 +20,7 @@ class TunModule(private val vpn: VpnService) : Module(vpn) { val portal: String, val dns: String, val disableIcmpForwarding: Boolean, + val icmpTimeout: Int, ) private val connectivity = service.getSystemService()!! @@ -63,6 +64,7 @@ class TunModule(private val vpn: VpnService) : Module(vpn) { portal = device.portal, dns = device.dns, disableIcmpForwarding = device.disableIcmpForwarding, + icmpTimeout = device.icmpTimeout, markSocket = vpn::protect, querySocketUid = this::queryUid ) diff --git a/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt b/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt index 562123b250..59a5914d41 100644 --- a/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt +++ b/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt @@ -66,6 +66,11 @@ class ServiceStore(context: Context) { defaultValue = true ) + var icmpTimeout by store.int( + key = "icmp_timeout", + defaultValue = 0 + ) + var dynamicNotification by store.boolean( key = "dynamic_notification", defaultValue = true From 51479b1d8bf96b8c61641f7c0c4f10b45ca24bbb Mon Sep 17 00:00:00 2001 From: sudoup Date: Wed, 16 Sep 2026 22:19:33 +0300 Subject: [PATCH 3/6] clamp icmp-timeout to non-negative and show its unit Negative input produced a negative ICMPTimeout, which sets a past read deadline and breaks ICMP forwarding with an immediate 'i/o timeout'. Coerce the parsed value to at least zero and trim whitespace, and label the option with the seconds unit. --- .../main/java/com/github/kr328/clash/design/preference/Value.kt | 2 +- design/src/main/res/values/strings.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/design/src/main/java/com/github/kr328/clash/design/preference/Value.kt b/design/src/main/java/com/github/kr328/clash/design/preference/Value.kt index 084d5cfaf5..7e6ce7a708 100644 --- a/design/src/main/java/com/github/kr328/clash/design/preference/Value.kt +++ b/design/src/main/java/com/github/kr328/clash/design/preference/Value.kt @@ -25,7 +25,7 @@ interface NullableTextAdapter { } override fun to(text: String?): Int { - return text?.toIntOrNull() ?: 0 + return text?.trim()?.toIntOrNull()?.coerceAtLeast(0) ?: 0 } } diff --git a/design/src/main/res/values/strings.xml b/design/src/main/res/values/strings.xml index 893dcacb81..d6cc8d03d4 100644 --- a/design/src/main/res/values/strings.xml +++ b/design/src/main/res/values/strings.xml @@ -137,7 +137,7 @@ Handle all dns packet Disable ICMP Forwarding Reply ICMP echo requests locally instead of forwarding them - ICMP Timeout + ICMP Timeout (seconds) Default Block Loopback Block loopback connections From 1940970990ea7d2bc094eb93f25f0805d06bc18a Mon Sep 17 00:00:00 2001 From: sudoup Date: Fri, 2 Oct 2026 13:26:30 +0300 Subject: [PATCH 4/6] Add manual build workflow with branch select --- .github/workflows/build-manual.yaml | 131 ++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 .github/workflows/build-manual.yaml diff --git a/.github/workflows/build-manual.yaml b/.github/workflows/build-manual.yaml new file mode 100644 index 0000000000..b6a8496819 --- /dev/null +++ b/.github/workflows/build-manual.yaml @@ -0,0 +1,131 @@ +name: Build Manual + +on: + workflow_dispatch: + inputs: + branch: + description: 'Ветка для сборки' + required: true + default: 'main' + type: branch + flavor: + description: 'Flavor сборки' + required: true + default: 'meta' + type: choice + options: + - meta + - alpha + +jobs: + BuildManual: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v6 + with: + ref: ${{ inputs.branch }} + fetch-depth: 0 + + - name: Checkout submodules + run: git submodule update --init --recursive --force + + - name: Setup Java + uses: actions/setup-java@v5 + with: + distribution: "temurin" + java-version: 21 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + + - name: Setup Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c + with: + go-download-base-url: 'https://github.com/MetaCubeX/go/releases/download/build' + go-version: '1.26' + + - name: Apply Patches + run: | + cd $(go env GOROOT) + for p in $GITHUB_WORKSPACE/.github/patch/*.patch; do patch --verbose -p 1 < "$p"; done + + - uses: actions/cache@v5 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Update CA + run: | + sudo apt-get update && sudo apt-get install ca-certificates + sudo update-ca-certificates + cp -f /etc/ssl/certs/ca-certificates.crt core/src/foss/golang/clash/component/ca/ca-certificates.crt + + - name: Restore Keystore + env: + KEYSTORE: ${{ secrets.KEYSTORE }} + run: | + echo "$KEYSTORE" | base64 --decode > release.keystore + + - name: Signing properties + env: + SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }} + SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }} + SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} + run: | + echo "keystore.password=$SIGNING_STORE_PASSWORD" > signing.properties + echo "key.alias=$SIGNING_KEY_ALIAS" >> signing.properties + echo "key.password=$SIGNING_KEY_PASSWORD" >> signing.properties + + - name: Manual Build + if: success() + run: | + if [ "${{ inputs.flavor }}" = "meta" ]; then + ./gradlew --no-daemon app:assembleMetaRelease + else + ./gradlew --no-daemon app:assembleAlphaRelease + fi + + - name: Upload APK (universal) + uses: actions/upload-artifact@v7 + if: ${{ success() }} + with: + name: CMFA-${{ inputs.flavor }}-${{ inputs.branch }}-universal + path: | + app/build/outputs/apk/${{ inputs.flavor }}/release/*-universal-*.apk + + - name: Upload APK (arm64-v8a) + uses: actions/upload-artifact@v7 + if: ${{ success() }} + with: + name: CMFA-${{ inputs.flavor }}-${{ inputs.branch }}-arm64-v8a + path: | + app/build/outputs/apk/${{ inputs.flavor }}/release/*-arm64-v8a-*.apk + + - name: Upload APK (armeabi-v7a) + uses: actions/upload-artifact@v7 + if: ${{ success() }} + with: + name: CMFA-${{ inputs.flavor }}-${{ inputs.branch }}-armeabi-v7a + path: | + app/build/outputs/apk/${{ inputs.flavor }}/release/*-armeabi-v7a-*.apk + + - name: Upload APK (x86_64) + uses: actions/upload-artifact@v7 + if: ${{ success() }} + with: + name: CMFA-${{ inputs.flavor }}-${{ inputs.branch }}-x86_64 + path: | + app/build/outputs/apk/${{ inputs.flavor }}/release/*-x86_64-*.apk + + - name: Upload APK (x86) + uses: actions/upload-artifact@v7 + if: ${{ success() }} + with: + name: CMFA-${{ inputs.flavor }}-${{ inputs.branch }}-x86 + path: | + app/build/outputs/apk/${{ inputs.flavor }}/release/*-x86-*.apk From 68c7af2c3550dd13469e52b06f56568d2aa71d66 Mon Sep 17 00:00:00 2001 From: sudoup Date: Fri, 2 Oct 2026 13:40:52 +0300 Subject: [PATCH 5/6] Update manual build: English, release upload, no artifacts --- .github/workflows/build-manual.yaml | 52 ++++++++--------------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/.github/workflows/build-manual.yaml b/.github/workflows/build-manual.yaml index b6a8496819..a08e0bd589 100644 --- a/.github/workflows/build-manual.yaml +++ b/.github/workflows/build-manual.yaml @@ -4,22 +4,28 @@ on: workflow_dispatch: inputs: branch: - description: 'Ветка для сборки' + description: 'Branch to build from' required: true default: 'main' type: branch flavor: - description: 'Flavor сборки' + description: 'Build flavor' required: true default: 'meta' type: choice options: - meta - alpha + release-tag: + description: 'Release tag' + required: true + default: 'manual' jobs: BuildManual: runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout Repository uses: actions/checkout@v6 @@ -90,42 +96,10 @@ jobs: ./gradlew --no-daemon app:assembleAlphaRelease fi - - name: Upload APK (universal) - uses: actions/upload-artifact@v7 - if: ${{ success() }} - with: - name: CMFA-${{ inputs.flavor }}-${{ inputs.branch }}-universal - path: | - app/build/outputs/apk/${{ inputs.flavor }}/release/*-universal-*.apk - - - name: Upload APK (arm64-v8a) - uses: actions/upload-artifact@v7 - if: ${{ success() }} - with: - name: CMFA-${{ inputs.flavor }}-${{ inputs.branch }}-arm64-v8a - path: | - app/build/outputs/apk/${{ inputs.flavor }}/release/*-arm64-v8a-*.apk - - - name: Upload APK (armeabi-v7a) - uses: actions/upload-artifact@v7 + - name: Upload Release + uses: softprops/action-gh-release@v3 if: ${{ success() }} with: - name: CMFA-${{ inputs.flavor }}-${{ inputs.branch }}-armeabi-v7a - path: | - app/build/outputs/apk/${{ inputs.flavor }}/release/*-armeabi-v7a-*.apk - - - name: Upload APK (x86_64) - uses: actions/upload-artifact@v7 - if: ${{ success() }} - with: - name: CMFA-${{ inputs.flavor }}-${{ inputs.branch }}-x86_64 - path: | - app/build/outputs/apk/${{ inputs.flavor }}/release/*-x86_64-*.apk - - - name: Upload APK (x86) - uses: actions/upload-artifact@v7 - if: ${{ success() }} - with: - name: CMFA-${{ inputs.flavor }}-${{ inputs.branch }}-x86 - path: | - app/build/outputs/apk/${{ inputs.flavor }}/release/*-x86-*.apk + tag_name: ${{ inputs.release-tag }} + files: app/build/outputs/apk/${{ inputs.flavor }}/release/*.apk + generate_release_notes: true From bd6ed97e771807ad07a0861d141d30414e24f681 Mon Sep 17 00:00:00 2001 From: sudoup Date: Fri, 2 Oct 2026 13:44:09 +0300 Subject: [PATCH 6/6] Disable setup-go cache to fix go.mod warning --- .github/workflows/build-manual.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-manual.yaml b/.github/workflows/build-manual.yaml index a08e0bd589..5d7ec6ab60 100644 --- a/.github/workflows/build-manual.yaml +++ b/.github/workflows/build-manual.yaml @@ -50,6 +50,7 @@ jobs: with: go-download-base-url: 'https://github.com/MetaCubeX/go/releases/download/build' go-version: '1.26' + cache: false - name: Apply Patches run: |