package pkg import ( "bufio" "errors" "fmt" "slices" "strings" // "slices" // "strconv" "os" "os/exec" "path/filepath" paths "qulay/config" "qulay/database" "qulay/helpers" "net/http" "time" "io" "context" "github.com/codeclysm/extract/v3" "codeberg.org/UzbekLinux/uzbekdb-go" ) var _name string = filepath.Base(os.Args[0]) var installing = map[string]bool{} type Package struct { Name string Repo string Data []interface{} } func randomTempDir(destDir string) string { tempDir := "qulay_" + helpers.RandStr(10) tempDirPath := filepath.Join(destDir, tempDir) os.MkdirAll(tempDirPath, 0755) return tempDirPath } func readPackage(path string) ([]string, string, string, string, string) { pkgPath := filepath.Join(path, "pkg") f, err := os.Open(pkgPath) if err != nil { return []string{}, "", "", "", "" } defer f.Close() var depends []string var taip = "" var url = "" var afterScript = "" var removeScript = "" scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "deps: ") { depends = append(depends, strings.Fields(strings.TrimPrefix(line, "deps: "))...) } else if strings.HasPrefix(line, "type: ") { taip = strings.TrimPrefix(line, "type: ") } else if strings.HasPrefix(line, "url: ") { url = strings.TrimPrefix(line, "url: ") } else if strings.HasPrefix(line, "after: ") { afterScript = strings.TrimPrefix(line, "after: ") } else if strings.HasPrefix(line, "remove: ") { removeScript = strings.TrimPrefix(line, "remove: ") } } return depends, taip, url, afterScript, removeScript } func GetPackages(destDir string) []Package { var found []Package entries, _ := os.ReadDir(filepath.Join(destDir, paths.ReposDir)) for _, e := range entries { if !e.IsDir() { continue } data, err := os.ReadFile(filepath.Join(destDir, paths.ReposDir, e.Name(), "pkgs")) if err != nil { continue } pkgs, ok := uzbekdb.Loads(string(data), "map").(map[interface{}][]interface{}) if !ok { continue } for name, val := range pkgs { found = append(found, Package{fmt.Sprintf("%v", name), e.Name(), val}) } } slices.SortFunc(found, func(a, b Package) int { if n := strings.Compare(a.Repo, b.Repo); n != 0 { return n } return strings.Compare(a.Name, b.Name) }) return found } func FindPackages(name string, destDir string) []Package { var found []Package var packages = GetPackages(destDir) var splitName = false var repo string if strings.Contains(name, "/") { splitName = true parts := strings.SplitN(name, "/", 2) repo = parts[0] name = parts[1] } for _, e := range packages { if splitName { if e.Name == name && e.Repo == repo { found = append(found, e) } } else { if e.Name == name { found = append(found, e) } } } return found } func getPackage(name string, destDir string) (*Package, error) { var pkgs []Package var pkg Package if strings.Contains(name, "/") { parts := strings.SplitN(name, "/", 2) repo := parts[0] name = parts[1] pkgs = FindPackages(repo+"/"+name, destDir) if len(pkgs) == 0 { return nil, errors.New("no packages found with name " + name + " in " + repo + " repository") } else if len(pkgs) == 1 { pkg = pkgs[0] } else { for _, pkg := range pkgs { fmt.Printf("%s/%s - %v - %v\n", pkg.Repo, pkg.Name, pkg.Data[0], pkg.Data[1]) } return nil, errors.New("? found multiple packages with name " + name + " in " + repo + " repository") } } else { pkgs = FindPackages(name, destDir) if len(pkgs) == 0 { return nil, errors.New("no packages found with name " + name + " in repositories") } else if len(pkgs) > 1 { for _, pkg := range pkgs { fmt.Printf("%s/%s - %v - %v\n", pkg.Repo, pkg.Name, pkg.Data[0], pkg.Data[1]) } return nil, errors.New("found multiple packages with name " + name + " in repositories") } else { pkg = pkgs[0] } } return &pkg, nil } func Install(name string, args []string, destDir string) error { // verbose := slices.Contains(args, ":v") reinstall := slices.Contains(args, ":r") disableDeps := slices.Contains(args, ":nd") pkg, err := getPackage(name, destDir) if err != nil { return err } pkgPath := filepath.Join(destDir, paths.ReposDir, pkg.Repo, "packages", pkg.Name) depends, taip, url, afterScript, _ := readPackage(pkgPath) key := pkg.Repo + "/" + pkg.Name if installing[key] { return nil } installing[key] = true defer func() { installing[key] = false }() if !disableDeps { if len(depends) != 0 { fmt.Println(helpers.ToString(len(depends)) + " deps for " + pkg.Repo + "/" + pkg.Name + ":") for i, n := range depends { if n == pkg.Name || n == pkg.Repo+"/"+pkg.Name { return errors.New("broken deps in package " + n) } depPkgs := FindPackages(n, destDir) v := "not found" if len(depPkgs) > 0 { v = fmt.Sprintf("%v", depPkgs[0].Data[1]) } fmt.Printf("%v. %s - %v\n", i+1, n, v) } } } if slices.Contains(args, ":a") { if !helpers.Ask("install " + pkg.Repo + "/" + pkg.Name + " with version " + helpers.ToString(pkg.Data[1])) { return errors.New("canceled by user") } } if !disableDeps { for _, dep := range depends { depPkg, err := getPackage(dep, destDir) if err != nil { return err } fullName := depPkg.Repo + "/" + depPkg.Name if !database.IsInstalled(fullName, destDir) || reinstall { err := Install(fullName, args, destDir) if err != nil { return err } } } } fmt.Println(":: installing " + pkg.Name + " from repo " + pkg.Repo) if taip == "arch" { destArchivePath := filepath.Join(destDir, "tmp", pkg.Name + pkg.Repo + ".tar.zst") fullDestPath := filepath.Join(pkgPath, "arch") os.MkdirAll(fullDestPath, 0755) os.MkdirAll(filepath.Dir(destArchivePath), 0755) client := &http.Client{ Timeout: 10 * time.Second, } resp, err := client.Get(url) if err != nil { fmt.Println("!! failed to download", url, err) return err } defer resp.Body.Close() f, err := os.Create(destArchivePath) if err != nil { fmt.Println("!! failed to create file", err) return err } defer f.Close() _, err = io.Copy(f, resp.Body) f.Close() if err != nil { fmt.Println("!! failed to copy", err) return err } fmt.Println(":: downloaded", url, "to", destArchivePath) _ = os.RemoveAll(fullDestPath) file, err := os.Open(destArchivePath) if err != nil { fmt.Println("!! failed to open archive file", destArchivePath, err) return err } defer file.Close() err = extract.Zstd(context.TODO(), file, fullDestPath, nil) if err != nil { fmt.Println("!! failed to extract", err) return err } fmt.Println(":: removing govnofiles from package...") _ = os.Remove(filepath.Join(fullDestPath, ".BUILDINFO")) _ = os.Remove(filepath.Join(fullDestPath, ".MTREE")) _ = os.Remove(filepath.Join(fullDestPath, ".PKGINFO")) _ = os.Remove(filepath.Join(fullDestPath, ".INSTALL")) fmt.Println(":: copying files to / ...") err = helpers.CopyDir(fullDestPath, filepath.Join(destDir, "/")) if err != nil { fmt.Println("!! failed to copy files to /:", err) } else { fmt.Println(":: successfully copied to /") } outputFilePath := filepath.Join(pkgPath, "files") outputFile, err := os.Create(outputFilePath) if err != nil { fmt.Println("!! failed to create files list:", err) return err } defer outputFile.Close() err = filepath.WalkDir(fullDestPath, func(path string, d os.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { return nil } relPath, err := filepath.Rel(fullDestPath, path) if err != nil { return err } formattedPath := "/" + filepath.ToSlash(relPath) _, err = outputFile.WriteString(formattedPath + "\n") return err }) if err != nil { fmt.Println("!! failed to index files:", err) return err } } else if taip == "script" { cmd := exec.Command("sh", "install.sh") cmd.Dir = pkgPath cmd.Env = append(os.Environ(), "DESTDIR="+destDir) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { return err } } else if taip == "binary" { client := &http.Client{ Timeout: 10 * time.Second, } resp, err := client.Get(url) if err != nil { return err } defer resp.Body.Close() outPath := filepath.Join(destDir, "/usr/bin", pkg.Name) f, err := os.Create(outPath) if err != nil { return err } _, err = io.Copy(f, resp.Body) f.Close() if err != nil { return err } err = os.Chmod(outPath, 0755) if err != nil { return err } } else { return errors.New("package err maybe old format") } if afterScript != "" { fmt.Println(":: running after installation script " + afterScript + " ...") cmd := exec.Command("sh", afterScript) cmd.Dir = pkgPath cmd.Env = append(os.Environ(), "DESTDIR="+destDir) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { return err } } err = database.RegPackage(pkg.Repo+"/"+pkg.Name, helpers.ToString(pkg.Data[1]), destDir) if err != nil { return errors.New("failed to register package in installed database: " + err.Error()) } fmt.Println(":: " + pkg.Repo + "/" + pkg.Name + " (" + helpers.ToString(pkg.Data[1]) + ") installed") return nil } func Remove(name string, args []string, destDir string) error { pkg, err := getPackage(name, destDir) if err != nil { return err } if slices.Contains(args, ":a") { if !helpers.Ask("remove " + pkg.Repo + "/" + pkg.Name + " with repo version " + helpers.ToString(pkg.Data[1]) + " and local version " + database.GetPackageVersion(pkg.Repo+"/"+pkg.Name, destDir) + "?") { return errors.New("canceled by user") } } pkgPath := filepath.Join(destDir, paths.ReposDir, pkg.Repo, "packages", pkg.Name) _, _, _, _, removeScript := readPackage(pkgPath) if removeScript != "" { fmt.Println(":: running remove script " + removeScript + " ...") cmd := exec.Command("sh", removeScript) cmd.Dir = pkgPath cmd.Env = os.Environ() cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { return err } } if _, err := os.Stat("files"); err == nil { f, _ := os.Open("files") defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { file := scanner.Text() if file == "" { continue } path := filepath.Join(destDir, file) if _, err := os.Stat(path); err == nil { fmt.Printf("removing %s\n", path) if err := os.RemoveAll(path); err != nil { fmt.Fprintf(os.Stderr, "failed to remove %s: %v\n", path, err) } } else if !os.IsNotExist(err) { fmt.Fprintf(os.Stderr, "failed to stat %s: %v\n", path, err) } } if err := scanner.Err(); err != nil { fmt.Fprintf(os.Stderr, "failed to read files: %v\n", err) } } if removeScript == "" { if _, err := os.Stat("files"); os.IsNotExist(err) { return errors.New("failed to remove package file (no remove script and paths file)") } } err = database.UnRegPackage(pkg.Repo+"/"+pkg.Name, destDir) if err != nil { return err } v := helpers.ToString(pkg.Data[1]) fmt.Println(":: " + pkg.Repo + "/" + pkg.Name + " (" + v + ") removed") return nil }