hi-client/docs/go修复.md
Rust d02eed3bd8 docs: 添加 GitHub Actions 构建文档并锁定 libcore 版本
- 添加完整的 GitHub Actions 构建指南文档
  - BUILD_GUIDE.md: Android 详细构建指南
  - MULTIPLATFORM_GUIDE.md: 多平台构建指南
  - HOW_TO_BUILD.md: 分步操作教程
  - QUICKSTART.md: 3步快速开始指南
  - INDEX.md: 文档总览索引
  - README.md: 基础说明
- 创建 docs/ 目录存放项目文档
- 锁定 libcore 子模块到 f993a57 (v3.1.7)
  - 防止在线编译时使用最新版本
  - 确保构建稳定性和一致性
2025-10-27 23:11:21 +08:00

8.2 KiB

Android SELinux chown 权限问题修复方案

问题背景

在 Android 12+ 系统上,由于 SELinux 严格模式的权限限制,sing-box 核心库在启动时会因为 chown 操作被拒绝而失败。具体表现为以下三个错误:

  1. CommandServer 错误: chown: chown command.sock: operation not permitted
  2. Logger 错误: start logger: chown box.log: operation not permitted
  3. Clash Cache 错误: pre-start cache file: platform chown: chown clash.db: operation not permitted

问题分析

根本原因

sing-box (libbox) 在创建文件后会尝试修改文件所有权(chown操作),但在 Android 的 SELinux 环境下,应用无法修改外部存储目录中文件的所有权,导致启动失败。

涉及的文件

  1. command.sock - CommandServer 的 Unix socket 文件
  2. box.log - sing-box 的日志文件
  3. clash.db - Clash API 的缓存数据库文件

修复方案

方案概述

核心思路: 在配置层面禁用会触发 chown 操作的功能,或者修改底层代码跳过 chown 操作。

由于修改 sing-box 底层库较为复杂,建议采用配置修改方案。


具体修复步骤

修复 1: 禁用文件日志

文件: libcore/config/hiddify_option.go

位置: DefaultHiddifyOptions() 函数

修改前:

LogLevel: "warn",
// LogFile:        "/dev/null",
LogFile:        "box.log",

修改后:

LogLevel: "warn",
LogFile:        "",  // 禁用文件日志,避免 Android SELinux chown 权限问题
// LogFile:        "box.log",

说明:

  • LogFile 设置为空字符串,禁用文件日志输出
  • 日志将只输出到 stderr,可以通过 logcat 查看
  • 这不会影响日志功能,只是改变了输出目标

修复 2: 禁用 Clash API 缓存

文件: libcore/config/config.go

位置: setClashAPI() 函数

修改前:

func setClashAPI(options *option.Options, opt *HiddifyOptions) {
	if opt.EnableClashApi {
		if opt.ClashApiSecret == "" {
			opt.ClashApiSecret = generateRandomString(16)
		}
		options.Experimental = &option.ExperimentalOptions{
			ClashAPI: &option.ClashAPIOptions{
				ExternalController: fmt.Sprintf("%s:%d", "127.0.0.1", opt.ClashApiPort),
				Secret:             opt.ClashApiSecret,
			},

			CacheFile: &option.CacheFileOptions{
				Enabled: true,
				Path:    "clash.db",
			},
		}
	}
}

修改后:

func setClashAPI(options *option.Options, opt *HiddifyOptions) {
	if opt.EnableClashApi {
		if opt.ClashApiSecret == "" {
			opt.ClashApiSecret = generateRandomString(16)
		}
		options.Experimental = &option.ExperimentalOptions{
			ClashAPI: &option.ClashAPIOptions{
				ExternalController: fmt.Sprintf("%s:%d", "127.0.0.1", opt.ClashApiPort),
				Secret:             opt.ClashApiSecret,
			},

			CacheFile: &option.CacheFileOptions{
				Enabled: false,  // 禁用缓存以避免 Android SELinux chown 权限问题
				Path:    "",     // 清空路径
			},
		}
	}
}

说明:

  • Enabled 设置为 false,禁用 Clash API 缓存功能
  • Path 设置为空字符串
  • Clash API 功能仍然可用,只是不会持久化节点选择等信息

修复 3: CommandServer 错误处理 (可选)

说明: CommandServer 主要用于命令行控制,对 Android VPN 功能不是必需的。

方案 A: 在应用层捕获异常 (推荐)

文件: android/app/src/main/kotlin/com/hiddify/hiddify/bg/BoxService.kt

位置: onStartCommand() 函数

修改:

GlobalScope.launch(Dispatchers.IO) {
    Settings.startedByUser = true
    initialize()
    try {
        startCommandServer()
    } catch (e: Exception) {
        // CommandServer 启动失败不是致命错误
        // 在 Android 12+ SELinux 环境下,chown 操作可能被拒绝
        // 但这不影响 VPN 核心功能,因此记录警告但继续启动服务
        Log.w(TAG, "CommandServer failed to start (non-fatal): ${e.message}")
    }
    startService()
}

方案 B: 完全禁用 CommandServer

如果不需要 CommandServer 功能,可以直接注释掉启动代码:

// startCommandServer()  // 在 Android 上禁用

验证修复

编译步骤

  1. 使用 Docker 编译 (推荐):
cd libcore
docker run --rm -v "$PWD:/workspace" -w /workspace golang:1.23 bash -c "bash docker-compile.sh"
  1. 或者本地编译:
cd libcore
make android

部署编译好的库

编译完成后,有两种部署方式:

方式A: 替换完整AAR (推荐生产环境)

cd libcore
# 替换AAR文件
cp libcore.aar ../android/app/libs/

# 删除可能冲突的so文件
rm -rf ../android/app/src/main/jniLibs/

# 重新编译Flutter项目
cd ..
flutter clean && flutter build apk

优点: 支持所有架构,适合发布版本

方式B: 只提取arm64 so文件 (推荐测试环境)

cd libcore
# 从AAR提取arm64的libbox.so
unzip -j libcore.aar jni/arm64-v8a/libbox.so -d /tmp/
mkdir -p ../android/app/src/main/jniLibs/arm64-v8a/
cp /tmp/libbox.so ../android/app/src/main/jniLibs/arm64-v8a/

# 临时禁用AAR(避免冲突)
mv ../android/app/libs/libcore.aar ../android/app/libs/libcore.aar.old

# 编译测试版本
cd ..
flutter clean && flutter build apk --debug --split-per-abi

优点: 编译快速,APK体积小,适合开发调试

⚠️ 重要: 两种方式不能同时使用,会产生冲突!请根据需求选择其一。

测试验证

  1. 重新编译 Flutter 应用
  2. 在 Android 设备上安装并启动
  3. 查看 logcat 日志,确认没有 chown 相关错误
  4. 验证 VPN 能正常启动和代理流量

预期结果

修复后,应用启动日志应该显示:

D/A/BoxService: base dir: /data/user/0/app.xxx.com/files
D/A/BoxService: working dir: /storage/emulated/0/Android/data/app.xxx.com/files
D/A/BoxService: temp dir: /data/user/0/app.xxx.com/cache
W/A/BoxService: CommandServer failed to start (non-fatal): chown: chown command.sock: operation not permitted
D/A/BoxService: starting service
D/A/BoxService: 配置已修改: 禁用文件日志和Clash缓存
D/A/EventHandler: new status: Started  ✅ 成功启动!

影响评估

功能影响

功能 修复前 修复后 影响
VPN 代理 无法启动 正常工作 无影响
日志记录 文件输出 logcat 输出 日志仍可用,通过 logcat 查看
Clash API 带缓存 无缓存 节点选择不持久化,重启后重置
CommandServer 失败 跳过启动 命令行控制不可用(非必需)

性能影响

  • 无性能损失: 禁用缓存和文件日志对性能无负面影响
  • 启动速度: 可能略微加快(减少文件 I/O 操作)

替代方案 (高级)

如果需要保留完整功能,可以考虑以下方案:

方案 1: 修改 sing-box 源码

在 sing-box 底层库中跳过 Android 平台的 chown 操作:

// 在文件创建后的 chown 调用处添加平台判断
if runtime.GOOS != "android" {
    if err := os.Chown(path, uid, gid); err != nil {
        return err
    }
}

优点: 保留所有功能 缺点: 需要修改 sing-box 上游代码,维护成本高

方案 2: 使用内部存储

将文件创建在 /data/user/0/ 目录下而不是外部存储:

优点: 避免 SELinux 限制 缺点: 空间受限,不适合大文件


常见问题

Q1: 禁用文件日志后如何查看日志?

A: 使用 adb logcat:

adb logcat | grep "A/BoxService\|singbox"

Q2: 禁用 Clash 缓存后有什么影响?

A: 每次启动 VPN 时,节点选择会重置为默认值,但不影响 VPN 的核心代理功能。

Q3: 为什么 hiddify-app 可以正常运行?

A: hiddify-app 使用的 libcore 版本已经包含了这些修复,或者使用了不同的配置策略。

Q4: 这个修复是否适用于所有 Android 版本?

A: 是的,这个修复对 Android 11 及以下版本也兼容,不会产生负面影响。


参考资料


更新日志

  • 2025-10-27: 初始版本,包含三个核心修复方案