Pass exec environment paths through the product entry #207
@@ -6,6 +6,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"agent-system-backend/internal/execenv"
|
||||
@@ -137,6 +138,9 @@ func runDoctor(ctx context.Context, root *execenv.Root, py string, sandbox bool)
|
||||
if prefix := root.PythonPrefix(); prefix != "" {
|
||||
mounts = append(mounts, prefix)
|
||||
}
|
||||
if resolved, err := filepath.EvalSymlinks(py); err == nil {
|
||||
py = resolved
|
||||
}
|
||||
if err := execenv.Doctor(ctx, py, pyPath, nil, sandbox, mounts); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "env doctor:", err)
|
||||
return 1
|
||||
|
||||
@@ -53,7 +53,9 @@ func (p Pip) Resolve(ctx context.Context, reqs []string, base Lock) (Result, err
|
||||
if err := os.WriteFile(cons, []byte(constraintText(base)), 0o644); err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
args := []string{"-m", "pip", "install", "--dry-run", "--report", "-", "--only-binary=:all:", "--constraint", cons}
|
||||
// --report 写文件。写到 stdout 时 pip 仍把 Collecting / Would install 打在同一条流上,JSON 解析会失败。
|
||||
report := filepath.Join(dir, "report.json")
|
||||
args := []string{"-m", "pip", "install", "--dry-run", "--report", report, "--only-binary=:all:", "--constraint", cons}
|
||||
args = append(args, reqs...)
|
||||
cmd := exec.CommandContext(ctx, p.python(), args...)
|
||||
var stdout, stderr bytes.Buffer
|
||||
@@ -70,7 +72,11 @@ func (p Pip) Resolve(ctx context.Context, reqs []string, base Lock) (Result, err
|
||||
}
|
||||
return Result{}, fmt.Errorf("execenv: pip resolve: %v: %s", err, firstLine(text))
|
||||
}
|
||||
return parsePipReport(stdout.Bytes(), base)
|
||||
raw, err := os.ReadFile(report)
|
||||
if err != nil {
|
||||
return Result{}, fmt.Errorf("execenv: pip report: %w", err)
|
||||
}
|
||||
return parsePipReport(raw, base)
|
||||
}
|
||||
|
||||
func parsePipReport(raw []byte, base Lock) (Result, error) {
|
||||
|
||||
@@ -203,7 +203,13 @@ func resolveInterpreter(name string) (string, error) {
|
||||
case "python3":
|
||||
if execPythonBin != "" {
|
||||
if st, err := os.Stat(execPythonBin); err == nil && !st.IsDir() {
|
||||
return execPythonBin, nil
|
||||
// current/bin/python3 是符号链接。沙箱用 tmpfs 盖住 /home 后只挂真实前缀,
|
||||
// 执行未解析的 current 路径会找不到文件。
|
||||
resolved, err := filepath.EvalSymlinks(execPythonBin)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resolved, nil
|
||||
}
|
||||
return "", fmt.Errorf("python3: configured interpreter missing: %s", execPythonBin)
|
||||
}
|
||||
|
||||
@@ -213,6 +213,28 @@ func TestExecEnvIncludesSkillsRoot(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveInterpreterFollowsManagedSymlink(t *testing.T) {
|
||||
prev := execPythonBin
|
||||
t.Cleanup(func() { execPythonBin = prev })
|
||||
dir := t.TempDir()
|
||||
link := filepath.Join(dir, "python3")
|
||||
if err := os.Symlink("/usr/bin/python3", link); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
execPythonBin = link
|
||||
got, err := resolveInterpreter("python3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want, err := filepath.EvalSymlinks("/usr/bin/python3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("resolved %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpreterExecConfigForKernel(t *testing.T) {
|
||||
cfg := InterpreterExecConfig()
|
||||
if cfg == nil {
|
||||
|
||||
@@ -213,6 +213,12 @@ type Options struct {
|
||||
// 仅会话工作目录与技能目录挂载、进程数限额、超时杀整个沙箱)。
|
||||
// DisableExecTools 优先级高于此选择。
|
||||
ExecBackend string
|
||||
// ExecDepsDir、ExecWorkspace、ExecEnvRoot 与基座 serve 同名。
|
||||
// 产品入口不解析这三项旗标时留空,link 改读同名环境变量
|
||||
//(AGENT_SYSTEM_EXEC_DEPS_DIR / AGENT_SYSTEM_EXEC_WORKSPACE / AGENT_SYSTEM_EXEC_ENV_ROOT)。
|
||||
ExecDepsDir string
|
||||
ExecWorkspace string
|
||||
ExecEnvRoot string
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -3,19 +3,37 @@
|
||||
package link
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"agent-system-backend/internal/app"
|
||||
"agent-system-backend/pkg/extension"
|
||||
)
|
||||
|
||||
func init() {
|
||||
extension.SetRunner(func(opt extension.Options) error {
|
||||
return app.Run(app.Options{
|
||||
Addr: opt.Addr, DataDir: opt.DataDir, PluginsDir: opt.PluginsDir, ForceMock: opt.Mock,
|
||||
StorageDSN: opt.StorageDSN, ArtifactsDir: opt.ArtifactsDir, AuthMode: opt.AuthMode,
|
||||
DisableUploadIndexing: opt.DisableUploadIndexing,
|
||||
DisableConvCardMirror: opt.DisableConvCardMirror,
|
||||
DisableExecTools: opt.DisableExecTools,
|
||||
ExecBackend: opt.ExecBackend,
|
||||
})
|
||||
return app.Run(appOptions(opt))
|
||||
})
|
||||
}
|
||||
|
||||
// appOptions 把产品启动参数映射到基座。执行环境三项产品旗标留空时读环境变量,
|
||||
// 与 cmd/agent-system 的 config 键一致,这样 svc 注入环境即可,不必改产品 main。
|
||||
func appOptions(opt extension.Options) app.Options {
|
||||
return app.Options{
|
||||
Addr: opt.Addr, DataDir: opt.DataDir, PluginsDir: opt.PluginsDir, ForceMock: opt.Mock,
|
||||
StorageDSN: opt.StorageDSN, ArtifactsDir: opt.ArtifactsDir, AuthMode: opt.AuthMode,
|
||||
DisableUploadIndexing: opt.DisableUploadIndexing,
|
||||
DisableConvCardMirror: opt.DisableConvCardMirror,
|
||||
DisableExecTools: opt.DisableExecTools,
|
||||
ExecBackend: opt.ExecBackend,
|
||||
ExecDepsDir: firstSet(opt.ExecDepsDir, os.Getenv("AGENT_SYSTEM_EXEC_DEPS_DIR")),
|
||||
ExecWorkspace: firstSet(opt.ExecWorkspace, os.Getenv("AGENT_SYSTEM_EXEC_WORKSPACE")),
|
||||
ExecEnvRoot: firstSet(opt.ExecEnvRoot, os.Getenv("AGENT_SYSTEM_EXEC_ENV_ROOT")),
|
||||
}
|
||||
}
|
||||
|
||||
func firstSet(explicit, fromEnv string) string {
|
||||
if explicit != "" {
|
||||
return explicit
|
||||
}
|
||||
return fromEnv
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package link
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"agent-system-backend/pkg/extension"
|
||||
)
|
||||
|
||||
func TestAppOptionsPrefersExplicitExecPaths(t *testing.T) {
|
||||
t.Setenv("AGENT_SYSTEM_EXEC_DEPS_DIR", "/from/env/deps")
|
||||
t.Setenv("AGENT_SYSTEM_EXEC_WORKSPACE", "overlay")
|
||||
t.Setenv("AGENT_SYSTEM_EXEC_ENV_ROOT", "/from/env/root")
|
||||
got := appOptions(extension.Options{
|
||||
ExecDepsDir: "/explicit/deps",
|
||||
ExecWorkspace: "direct",
|
||||
ExecEnvRoot: "/explicit/root",
|
||||
})
|
||||
if got.ExecDepsDir != "/explicit/deps" || got.ExecWorkspace != "direct" || got.ExecEnvRoot != "/explicit/root" {
|
||||
t.Fatalf("explicit paths lost: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppOptionsReadsExecEnvWhenUnset(t *testing.T) {
|
||||
t.Setenv("AGENT_SYSTEM_EXEC_DEPS_DIR", "/from/env/deps")
|
||||
t.Setenv("AGENT_SYSTEM_EXEC_WORKSPACE", "overlay")
|
||||
t.Setenv("AGENT_SYSTEM_EXEC_ENV_ROOT", "/from/env/root")
|
||||
got := appOptions(extension.Options{ExecBackend: "sandbox"})
|
||||
if got.ExecBackend != "sandbox" || got.ExecDepsDir != "/from/env/deps" || got.ExecWorkspace != "overlay" || got.ExecEnvRoot != "/from/env/root" {
|
||||
t.Fatalf("env fallback: %+v", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user