diff options
| author | huker667 <huker@tuta.io> | 2026-07-15 14:33:08 +0300 |
|---|---|---|
| committer | huker667 <huker@tuta.io> | 2026-07-15 14:33:08 +0300 |
| commit | 22359129afeef78e0fcd56e62586819c06950846 (patch) | |
| tree | ac83fef674adb3e5527b9e5ee53246d43fb8be1b | |
| parent | d64b4f39839e112d096b9809fdb35e873a1b40fd (diff) | |
| download | qulay-22359129afeef78e0fcd56e62586819c06950846.tar.gz qulay-22359129afeef78e0fcd56e62586819c06950846.tar.bz2 qulay-22359129afeef78e0fcd56e62586819c06950846.zip | |
init commit da
| -rw-r--r-- | go.mod | 6 | ||||
| -rw-r--r-- | go.sum | 8 | ||||
| -rw-r--r-- | helpers/helpers.go | 60 | ||||
| -rw-r--r-- | main.go | 6 | ||||
| -rw-r--r-- | package/package.go | 255 |
5 files changed, 300 insertions, 35 deletions
@@ -1,9 +1,9 @@ module qulay -go 1.25.8 +go 1.26.4 require ( - codeberg.org/UzbekLinux/uzbekdb-go v0.0.0-20260324144756-3e62f214b3bb + codeberg.org/UzbekLinux/uzbekdb-go v0.0.0-20260509165828-8f8f4cbf599e github.com/codeclysm/extract/v3 v3.1.1 ) @@ -11,7 +11,7 @@ require ( github.com/h2non/filetype v1.1.3 // indirect github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 // indirect github.com/juju/loggo v1.0.0 // indirect - github.com/klauspost/compress v1.18.5 // indirect + github.com/klauspost/compress v1.15.13 // indirect github.com/kr/text v0.2.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect @@ -1,5 +1,5 @@ -codeberg.org/UzbekLinux/uzbekdb-go v0.0.0-20260324144756-3e62f214b3bb h1:iUv2IrVCC3MrUy5Q/4kPIk/X5npUCZn1zQe4cUgh6Vk= -codeberg.org/UzbekLinux/uzbekdb-go v0.0.0-20260324144756-3e62f214b3bb/go.mod h1:1npzE6+Q2MW4RuDTFcL+V9REv0o9k8LlTRW8jgGqeGA= +codeberg.org/UzbekLinux/uzbekdb-go v0.0.0-20260509165828-8f8f4cbf599e h1:CgMXKGmi0xnhKkQz/aiOKFpGF0RTn7Wc765ZKzymL+E= +codeberg.org/UzbekLinux/uzbekdb-go v0.0.0-20260509165828-8f8f4cbf599e/go.mod h1:1npzE6+Q2MW4RuDTFcL+V9REv0o9k8LlTRW8jgGqeGA= github.com/arduino/go-paths-helper v1.2.0 h1:qDW93PR5IZUN/jzO4rCtexiwF8P4OIcOmcSgAYLZfY4= github.com/arduino/go-paths-helper v1.2.0/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= github.com/codeclysm/extract/v3 v3.1.1 h1:iHZtdEAwSTqPrd+1n4jfhr1qBhUWtHlMTjT90+fJVXg= @@ -16,8 +16,8 @@ github.com/juju/loggo v1.0.0 h1:Y6ZMQOGR9Aj3BGkiWx7HBbIx6zNwNkxhVNOHU2i1bl0= github.com/juju/loggo v1.0.0/go.mod h1:NIXFioti1SmKAlKNuUwbMenNdef59IF52+ZzuOmHYkg= github.com/juju/testing v0.0.0-20200510222523-6c8c298c77a0 h1:+WWUkhnTjV6RNOxkcwk79qrjeyHEHvBzlneueBsatX4= github.com/juju/testing v0.0.0-20200510222523-6c8c298c77a0/go.mod h1:hpGvhGHPVbNBraRLZEhoQwFLMrjK8PSlO4D3nDjKYXo= -github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= -github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0= +github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= diff --git a/helpers/helpers.go b/helpers/helpers.go index 1db691b..2b4763a 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -3,9 +3,12 @@ package helpers import ( "fmt" "strings" + "path/filepath" "math/rand" "time" "strconv" + "os" + "io" ) var r = rand.New(rand.NewSource(time.Now().UnixNano())) @@ -42,3 +45,60 @@ func RandStr(n int) string { } return string(b) } + +func CopyDir(src, dst string) error { + return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + rel, err := filepath.Rel(src, path) + if err != nil { + return err + } + + target := filepath.Join(dst, rel) + + mode := info.Mode() + + switch { + case mode&os.ModeSymlink != 0: + link, err := os.Readlink(path) + if err != nil { + return err + } + return os.Symlink(link, target) + + case info.IsDir(): + return os.MkdirAll(target, 0755) + + default: + return CopyFile(path, target) + } + }) +} + +func CopyFile(src, dst string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + + si, err := os.Stat(src) + if err != nil { + return err + } + return os.Chmod(dst, si.Mode()) +} @@ -35,7 +35,6 @@ Options: :a -> ask before doing :nl -> disable recording in databases and logs :nd -> do not download deps - :nrc -> disable uid 0 (root) checks :r -> reinstall already installed deps Vars: DESTDIR -> path where the package will be installed/removed` @@ -79,11 +78,6 @@ func main() { os.Exit(1) } - if os.Getuid() != 0 && slices.Contains(_root_cmds, cmds[0]) && !slices.Contains(opts, ":nrc") { - fmt.Println("!! this command is root-only. run this with root privileges") - os.Exit(1) - } - // fmt.Println(cmds, oths, opts) if cmds[0] == "h" { printHelp() diff --git a/package/package.go b/package/package.go index 826b2ad..79d6e04 100644 --- a/package/package.go +++ b/package/package.go @@ -16,11 +16,19 @@ import ( "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 @@ -34,28 +42,36 @@ func randomTempDir(destDir string) string { return tempDirPath } -func readDepends(path string) []string { - dependsPath := filepath.Join(path, "depends") - dependsPath2 := filepath.Join(path, "depends.uz") - f, err := os.Open(dependsPath) +func readPackage(path string) ([]string, string, string, string, string) { + pkgPath := filepath.Join(path, "pkg") + + f, err := os.Open(pkgPath) if err != nil { - f, err = os.Open(dependsPath2) - if err != nil { - return []string{} - } + return []string{}, "", "", "", "" } defer f.Close() var depends []string + var taip = "" + var url = "" + var afterScript = "" + var removeScript = "" + scanner := bufio.NewScanner(f) for scanner.Scan() { - depends = append(depends, scanner.Text()) + 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 -} - -func isNewFormat(path string) bool { - _, err := os.Stat(filepath.Join(path, "data")) - return !os.IsNotExist(err) + return depends, taip, url, afterScript, removeScript } func GetPackages(destDir string) []Package { @@ -157,13 +173,24 @@ func Install(name string, args []string, destDir string) error { } pkgPath := filepath.Join(destDir, paths.ReposDir, pkg.Repo, "packages", pkg.Name) - depends := readDepends(pkgPath) + 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)) + " depends for " + pkg.Repo + "/" + pkg.Name + ":") + 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 depends in package " + n) + return errors.New("broken deps in package " + n) } depPkgs := FindPackages(n, destDir) @@ -176,11 +203,13 @@ func Install(name string, args []string, destDir string) error { } } } + 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) @@ -199,7 +228,107 @@ func Install(name string, args []string, destDir string) error { } } - if !isNewFormat(pkgPath) { + 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) @@ -209,6 +338,49 @@ func Install(name string, args []string, destDir string) error { 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) @@ -234,18 +406,57 @@ func Remove(name string, args []string, destDir string) error { pkgPath := filepath.Join(destDir, paths.ReposDir, pkg.Repo, "packages", pkg.Name) - if !isNewFormat(pkgPath) { - cmd := exec.Command("sh", "remove.sh") + _, _, _, _, 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() + 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 |