Skip to content

Commit 87424fe

Browse files
committed
fix:使用proot执行
1 parent 6a647fc commit 87424fe

File tree

5 files changed

+188
-4
lines changed

5 files changed

+188
-4
lines changed

.github/workflows/build.yml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,26 @@ jobs:
125125
mv /tmp/goecs jniLibs/x86_64/libgoecs.so
126126
chmod 755 jniLibs/x86_64/libgoecs.so
127127
128+
# 下载 proot(用于在 Android 上运行 Linux 二进制文件)
129+
echo "下载 proot..."
130+
PROOT_VERSION="5.4.0"
131+
132+
# ARM64 proot
133+
curl -L "https://github.com/proot-me/proot/releases/download/v${PROOT_VERSION}/proot-v${PROOT_VERSION}-aarch64-static" \
134+
-o jniLibs/arm64-v8a/libproot.so
135+
chmod 755 jniLibs/arm64-v8a/libproot.so
136+
137+
# x86_64 proot
138+
curl -L "https://github.com/proot-me/proot/releases/download/v${PROOT_VERSION}/proot-v${PROOT_VERSION}-x86_64-static" \
139+
-o jniLibs/x86_64/libproot.so
140+
chmod 755 jniLibs/x86_64/libproot.so
141+
128142
echo ""
129143
echo "jniLibs 文件列表:"
130-
ls -lh jniLibs/*/libgoecs.so
144+
ls -lh jniLibs/*/*.so
131145
echo ""
132146
echo "文件大小:"
133-
du -sh jniLibs/*/libgoecs.so
147+
du -sh jniLibs/*/*.so
134148
env:
135149
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
136150

embedding/proot_android.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//go:build android
2+
3+
package embedding
4+
5+
import (
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
"runtime"
10+
)
11+
12+
// GetProotPath 获取 proot 二进制文件路径
13+
// proot 允许在 Android 上运行 Linux 二进制文件
14+
func GetProotPath() (string, error) {
15+
libDir, err := findNativeLibraryDir()
16+
if err != nil {
17+
return "", fmt.Errorf("无法找到 lib 目录: %v", err)
18+
}
19+
20+
// proot 文件名
21+
var prootName string
22+
switch runtime.GOARCH {
23+
case "arm64":
24+
prootName = "libproot.so"
25+
case "arm":
26+
prootName = "libproot.so"
27+
case "amd64":
28+
prootName = "libproot_x86_64.so"
29+
case "386":
30+
prootName = "libproot_x86.so"
31+
default:
32+
prootName = "libproot.so"
33+
}
34+
35+
// 可能的子目录
36+
abiDirs := []string{"", "arm64-v8a", "arm64", "x86_64", "x86"}
37+
38+
for _, abiDir := range abiDirs {
39+
baseDir := libDir
40+
if abiDir != "" {
41+
baseDir = filepath.Join(libDir, abiDir)
42+
}
43+
44+
prootPath := filepath.Join(baseDir, prootName)
45+
if info, err := os.Stat(prootPath); err == nil && !info.IsDir() {
46+
// 确保有执行权限
47+
os.Chmod(prootPath, 0755)
48+
return prootPath, nil
49+
}
50+
}
51+
52+
return "", fmt.Errorf("找不到 proot 二进制文件")
53+
}
54+
55+
// SetupProotEnvironment 设置 proot 运行环境
56+
func SetupProotEnvironment() (map[string]string, error) {
57+
env := make(map[string]string)
58+
59+
// 获取应用的私有数据目录
60+
homeDir := os.Getenv("HOME")
61+
if homeDir == "" {
62+
// 尝试使用应用数据目录
63+
homeDir = "/data/data/com.oneclickvirt.goecs"
64+
if _, err := os.Stat(homeDir); os.IsNotExist(err) {
65+
// 如果无法访问,使用临时目录
66+
homeDir = "/data/local/tmp"
67+
}
68+
}
69+
70+
env["HOME"] = homeDir
71+
env["TMPDIR"] = filepath.Join(homeDir, "tmp")
72+
env["PATH"] = "/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin"
73+
74+
// 创建必要的目录
75+
os.MkdirAll(env["TMPDIR"], 0755)
76+
77+
return env, nil
78+
}
79+
80+
// BuildProotCommand 构建使用 proot 运行的命令
81+
// prootPath: proot 二进制文件路径
82+
// ecsPath: ECS 二进制文件路径
83+
// args: ECS 命令参数
84+
func BuildProotCommand(prootPath, ecsPath string, args []string) (string, []string) {
85+
// proot 参数
86+
prootArgs := []string{
87+
"-0", // 模拟 root 用户
88+
"-r", "/", // 根目录(不改变)
89+
"-w", "/", // 工作目录
90+
"--link2symlink", // 将硬链接转换为符号链接
91+
}
92+
93+
// 添加 ECS 命令和参数
94+
prootArgs = append(prootArgs, ecsPath)
95+
prootArgs = append(prootArgs, args...)
96+
97+
return prootPath, prootArgs
98+
}

ui/executor.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"os/exec"
7+
"runtime"
78
"strings"
89
"sync"
910

@@ -158,8 +159,20 @@ func (e *CommandExecutor) Execute() error {
158159
args := e.buildCommandArgs()
159160
e.mu.Unlock()
160161

161-
// 创建命令
162-
cmd := exec.CommandContext(e.cancelCtx, e.ecsPath, args...)
162+
var cmd *exec.Cmd
163+
var cmdStr string
164+
165+
// 在 Android 上使用 proot 运行 Linux 二进制文件
166+
if runtime.GOOS == "android" {
167+
// Android 平台:尝试使用 proot,如果失败则直接运行
168+
cmd, cmdStr = e.createAndroidCommand(args)
169+
} else {
170+
// 非 Android 平台直接运行
171+
cmd = exec.CommandContext(e.cancelCtx, e.ecsPath, args...)
172+
cmdStr = fmt.Sprintf("执行命令: %s %s", e.ecsPath, strings.Join(args, " "))
173+
}
174+
175+
e.ui.Terminal.AppendText(cmdStr + "\n\n")
163176

164177
// 直接将stdout和stderr连接到终端widget
165178
stdout, err := cmd.StdoutPipe()

ui/ui_executor_android.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//go:build android
2+
3+
package ui
4+
5+
import (
6+
"fmt"
7+
"os/exec"
8+
"strings"
9+
10+
"github.com/oneclickvirt/ecs-android/embedding"
11+
)
12+
13+
// createAndroidCommand 在 Android 平台创建命令
14+
// 尝试使用 proot 运行 Linux 二进制文件,如果失败则直接运行
15+
func (e *CommandExecutor) createAndroidCommand(args []string) (*exec.Cmd, string) {
16+
// 尝试获取 proot
17+
prootPath, err := embedding.GetProotPath()
18+
if err != nil {
19+
// 如果没有 proot,尝试直接运行(可能会失败)
20+
e.ui.Terminal.AppendText(fmt.Sprintf("⚠️ 警告: 找不到 proot (%v),尝试直接运行...\n\n", err))
21+
cmd := exec.CommandContext(e.cancelCtx, e.ecsPath, args...)
22+
cmdStr := fmt.Sprintf("执行命令: %s %s", e.ecsPath, strings.Join(args, " "))
23+
return cmd, cmdStr
24+
}
25+
26+
// 使用 proot 运行
27+
e.ui.Terminal.AppendText("✓ 使用 proot 环境运行 Linux 二进制文件\n\n")
28+
29+
prootCmd, prootArgs := embedding.BuildProotCommand(prootPath, e.ecsPath, args)
30+
cmd := exec.CommandContext(e.cancelCtx, prootCmd, prootArgs...)
31+
cmdStr := fmt.Sprintf("通过 proot 执行: %s %s", e.ecsPath, strings.Join(args, " "))
32+
33+
// 设置环境变量
34+
if env, err := embedding.SetupProotEnvironment(); err == nil {
35+
for k, v := range env {
36+
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v))
37+
}
38+
}
39+
40+
return cmd, cmdStr
41+
}

ui/ui_executor_noandroid.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//go:build !android
2+
3+
package ui
4+
5+
import (
6+
"fmt"
7+
"os/exec"
8+
"strings"
9+
)
10+
11+
// createAndroidCommand 在非 Android 平台的占位函数
12+
// 这个函数在非 Android 平台不会被调用,但需要存在以满足编译
13+
func (e *CommandExecutor) createAndroidCommand(args []string) (*exec.Cmd, string) {
14+
// 非 Android 平台直接运行
15+
cmd := exec.CommandContext(e.cancelCtx, e.ecsPath, args...)
16+
cmdStr := fmt.Sprintf("执行命令: %s %s", e.ecsPath, strings.Join(args, " "))
17+
return cmd, cmdStr
18+
}

0 commit comments

Comments
 (0)