164 lines
5.5 KiB
Go
164 lines
5.5 KiB
Go
package sandbox
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ssgo/httpclient"
|
|
"github.com/ssgo/log"
|
|
"github.com/ssgo/u"
|
|
)
|
|
|
|
func init() {
|
|
RegisterRuntime("nodejs", &Runtime{
|
|
Check: func(runtimePath, venvPath, projectPath string, uid, gid int, cfg *RuntimeConfig) (*RuntimeSandboxConfig, error) {
|
|
if err := checkNodejsRuntime(runtimePath, cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := checkNodejsProject(runtimePath, projectPath, uid, gid, cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nodeExe := getNodeExe(runtimePath)
|
|
return &RuntimeSandboxConfig{
|
|
StartCmd: nodeExe,
|
|
Envs: map[string]string{
|
|
"PATH": filepath.Dir(nodeExe) + string(os.PathListSeparator) + "$PATH",
|
|
},
|
|
}, nil
|
|
},
|
|
})
|
|
}
|
|
|
|
func checkNodejsRuntime(runtimePath string, cfg *RuntimeConfig) error {
|
|
runtimeVersion := filepath.Base(runtimePath)
|
|
lock := getRuntimeLock("nodejs", "runtime", runtimePath)
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
if !u.FileExists(runtimePath) {
|
|
osPart := u.StringIf(runtime.GOOS == "linux", "linux", u.StringIf(runtime.GOOS == "windows", "win", "darwin"))
|
|
archPart := u.StringIf(runtime.GOARCH == "arm64", "arm64", "x64")
|
|
extension := u.StringIf(runtime.GOOS == "windows", ".zip", ".tar.gz")
|
|
instFile := filepath.Join(filepath.Dir(runtimePath), ".installs", fmt.Sprintf("node-v%s-%s-%s%s", runtimeVersion, osPart, archPart, extension))
|
|
if !u.FileExists(instFile) {
|
|
downloadClient := httpclient.GetClient(3600 * time.Second)
|
|
downloadClient.EnableRedirect()
|
|
if cfg.HttpProxy != "" {
|
|
if proxyURL, err := url.Parse(cfg.HttpProxy); err == nil {
|
|
downloadClient.GetRawClient().Transport = &http.Transport{Proxy: http.ProxyURL(proxyURL)}
|
|
}
|
|
}
|
|
log.DefaultLogger.Info("[Sandbox] fetching nodejs runtime release list", "from", "https://nodejs.org/dist/index.json", "httpProxy", cfg.HttpProxy)
|
|
r1 := downloadClient.Get("https://nodejs.org/dist/index.json")
|
|
if r1.Error != nil {
|
|
return r1.Error
|
|
}
|
|
|
|
// 解析JSON数据
|
|
var versions []map[string]interface{}
|
|
if err := r1.To(&versions); err != nil {
|
|
return fmt.Errorf("failed to parse index.json: %v", err)
|
|
}
|
|
|
|
// 寻找匹配的版本
|
|
var targetVersion string
|
|
for _, v := range versions {
|
|
version := v["version"].(string)
|
|
// 检查版本号是否匹配
|
|
if strings.HasPrefix(version, "v"+runtimeVersion) {
|
|
// 检查是否有对应的文件
|
|
files := v["files"].([]interface{})
|
|
fileFound := false
|
|
for _, f := range files {
|
|
file := f.(string)
|
|
// 检查是否有对应的平台和架构的文件
|
|
if (osPart == "linux" && file == "linux-"+archPart) ||
|
|
(osPart == "darwin" && (file == "osx-"+archPart+"-tar" || file == "osx-x64-tar")) ||
|
|
(osPart == "win" && file == "win-"+archPart+"-zip") {
|
|
fileFound = true
|
|
break
|
|
}
|
|
}
|
|
if fileFound {
|
|
targetVersion = version
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if targetVersion == "" {
|
|
return fmt.Errorf("no binary for version %s on %s-%s", runtimeVersion, osPart, archPart)
|
|
}
|
|
|
|
// 构建下载URL
|
|
fileName := fmt.Sprintf("node-%s-%s-%s%s", targetVersion, osPart, archPart, extension)
|
|
downloadURL := fmt.Sprintf("https://nodejs.org/dist/%s/%s", targetVersion, fileName)
|
|
|
|
log.DefaultLogger.Info("[Sandbox] downloading nodejs runtime", "from", downloadURL, "to", instFile, "httpProxy", cfg.HttpProxy)
|
|
if _, err := downloadClient.Download(instFile, downloadURL, nil); err != nil {
|
|
return err
|
|
} else {
|
|
log.DefaultLogger.Info("[Sandbox] downloaded nodejs runtime", "version", targetVersion, "os", osPart, "arch", archPart, "url", downloadURL, "to", instFile)
|
|
}
|
|
}
|
|
|
|
os.MkdirAll(runtimePath, 0755)
|
|
if err := u.Extract(instFile, runtimePath, true); err != nil {
|
|
log.DefaultLogger.Error("[Sandbox] unpack nodejs runtime failed", "version", runtimeVersion, "os", osPart, "arch", archPart, "from", instFile, "err", err.Error())
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkNodejsProject(runtimePath, projectPath string, uid, gid int, cfg *RuntimeConfig) error {
|
|
lock := getRuntimeLock("nodejs", "project", projectPath)
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
if uid != 0 && runtime.GOOS == "linux" {
|
|
os.Chown(projectPath, uid, gid)
|
|
}
|
|
packageFile := filepath.Join(projectPath, "package.json")
|
|
if u.FileExists(packageFile) {
|
|
shaFile := filepath.Join(projectPath, ".package.sha1")
|
|
currentSha := u.Sha1String(u.ReadFileN(packageFile))
|
|
oldSha := u.ReadFileN(shaFile)
|
|
if oldSha != currentSha {
|
|
npmExe := getNpmExe(runtimePath)
|
|
npmArgs := []string{"install", "--prefix", projectPath, "--cache", filepath.Join(projectPath, ".npm")}
|
|
if cfg.Mirror != "" {
|
|
npmArgs = append(npmArgs, "--registry", cfg.Mirror)
|
|
}
|
|
|
|
if out, err := RunCmdWithEnv(map[string]string{"PATH": filepath.Dir(npmExe) + string(os.PathListSeparator) + os.Getenv("PATH")}, projectPath, uid, gid, npmExe, npmArgs...); err != nil {
|
|
log.DefaultLogger.Error("[Sandbox] npm install failed", "runtime", runtimePath, "project", projectPath, "err", err.Error(), "output", out)
|
|
return err
|
|
} else {
|
|
log.DefaultLogger.Info("[Sandbox] npm install success", "runtime", runtimePath, "project", projectPath, "output", out)
|
|
u.WriteFile(shaFile, currentSha)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getNodeExe(base string) string {
|
|
if runtime.GOOS == "windows" {
|
|
return filepath.Join(base, "node.exe")
|
|
}
|
|
return filepath.Join(base, "bin", "node")
|
|
}
|
|
|
|
func getNpmExe(base string) string {
|
|
if runtime.GOOS == "windows" {
|
|
return filepath.Join(base, "npm.cmd")
|
|
}
|
|
return filepath.Join(base, "bin", "npm")
|
|
}
|