package executil import ( "bytes" "fmt" "io" "log" "os" "os/exec" "path" "strings" "zlh-agent/scripts" ) // RunEmbeddedScript executes an embedded script via bash by piping its contents to stdin. // If the script is a devcontainer installer, this automatically prepends the shared common.sh // so that tiny per-runtime installers can call install_runtime in the same shell session. func RunEmbeddedScript(scriptPath string, extraEnv ...string) error { normalized := normalizeEmbeddedPath(scriptPath) payload, err := loadEmbeddedPayload(normalized) if err != nil { return err } cmd := exec.Command("bash") cmd.Stdin = bytes.NewReader(payload) // Inherit current env and overlay any provided vars (e.g., RUNTIME_VERSION=24). if len(extraEnv) > 0 { cmd.Env = append(os.Environ(), extraEnv...) } // Match RunScript behavior (executil.go) var buf bytes.Buffer cmd.Stdout = io.MultiWriter(os.Stdout, &buf) cmd.Stderr = io.MultiWriter(os.Stderr, &buf) log.Printf("[provision] action=run_embedded_script path=%s", normalized) if err := cmd.Run(); err != nil { log.Printf("[provision] action=run_embedded_script status=failed path=%s err=%v", normalized, err) for _, line := range tailLogLines(buf.String(), 10) { log.Printf("[provision] path=%s output=%s", normalized, line) } return err } log.Printf("[provision] action=run_embedded_script status=ok path=%s", normalized) return nil } func loadEmbeddedPayload(normalized string) ([]byte, error) { // If this is a devcontainer installer, prepend the shared library. if strings.HasPrefix(normalized, "devcontainer/") { commonPath := "devcontainer/lib/common.sh" common, err := scripts.FS.ReadFile(commonPath) if err != nil { return nil, fmt.Errorf("embedded script missing: %s: %w", commonPath, err) } body, err := scripts.FS.ReadFile(normalized) if err != nil { return nil, fmt.Errorf("embedded script missing: %s: %w", normalized, err) } // Ensure bash sees common first, then runtime installer. return append(append(common, '\n'), body...), nil } data, err := scripts.FS.ReadFile(normalized) if err != nil { return nil, fmt.Errorf("embedded script missing: %s: %w", normalized, err) } return data, nil } func normalizeEmbeddedPath(p string) string { p = strings.TrimPrefix(p, "scripts/") p = path.Clean(p) return strings.TrimPrefix(p, "/") }