summaryrefslogtreecommitdiff
path: root/package
diff options
context:
space:
mode:
authorhuker667 <huker@tuta.io>2026-08-11 18:58:45 +0300
committerhuker667 <huker@tuta.io>2026-08-11 18:58:45 +0300
commitd6ebd95cbdbd98b959e52122f2924621b510d291 (patch)
treea8a176a2c7186d2a99b2c214a2f6a4aa9af95c0c /package
parent22359129afeef78e0fcd56e62586819c06950846 (diff)
downloadqulay-main.tar.gz
qulay-main.tar.bz2
qulay-main.zip
ебать крупнейшее обновление 1.0HEADmain
Diffstat (limited to 'package')
-rw-r--r--package/package.go1822
1 files changed, 1573 insertions, 249 deletions
diff --git a/package/package.go b/package/package.go
index 79d6e04..6a2aff8 100644
--- a/package/package.go
+++ b/package/package.go
@@ -1,28 +1,28 @@
package pkg
import (
+ "archive/tar"
"bufio"
+ "context"
"errors"
"fmt"
- "slices"
- "strings"
-
- // "slices"
- // "strconv"
+ "io"
+ "net/http"
"os"
"os/exec"
"path/filepath"
+ "regexp"
+ "runtime"
+ "slices"
+ "strconv"
+ "strings"
paths "qulay/config"
"qulay/database"
"qulay/helpers"
- "net/http"
- "time"
- "io"
- "context"
-
- "github.com/codeclysm/extract/v3"
"codeberg.org/UzbekLinux/uzbekdb-go"
+ "github.com/codeclysm/extract/v3"
+ "github.com/klauspost/compress/zstd"
)
var _name string = filepath.Base(os.Args[0])
@@ -30,188 +30,1154 @@ var _name string = filepath.Base(os.Args[0])
var installing = map[string]bool{}
type Package struct {
- Name string
- Repo string
- Data []interface{}
-}
+ Name string
+ Repo string
+ Version string
+ Desc string
+
+ Depends []string
+ Type string
+ URL string
-func randomTempDir(destDir string) string {
- tempDir := "qulay_" + helpers.RandStr(10)
- tempDirPath := filepath.Join(destDir, tempDir)
- os.MkdirAll(tempDirPath, 0755)
- return tempDirPath
+ Conflicts []string
+
+ After string
+ Remove string
+
+ Message string
+
+ Path string
}
-func readPackage(path string) ([]string, string, string, string, string) {
- pkgPath := filepath.Join(path, "pkg")
-
- f, err := os.Open(pkgPath)
+func readPackage(repo string, path string, repoURL string) (*Package, error) {
+ f, err := os.Open(filepath.Join(path, "manifest"))
if err != nil {
- return []string{}, "", "", "", ""
+ return nil, err
}
defer f.Close()
- var depends []string
- var taip = ""
- var url = ""
- var afterScript = ""
- var removeScript = ""
+
+ pkg := &Package{
+ Repo: repo,
+ Path: path,
+ }
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: ")
+ line := strings.TrimSpace(scanner.Text())
+
+ if line == "" || strings.HasPrefix(line, "#") {
+ continue
+ }
+
+ switch {
+ case strings.HasPrefix(line, "name:"):
+ pkg.Name = strings.TrimSpace(strings.TrimPrefix(line, "name:"))
+
+ case strings.HasPrefix(line, "repo:"):
+ if repo == "" {
+ repo = strings.TrimSpace(strings.TrimPrefix(line, "repo:"))
+ }
+
+ case strings.HasPrefix(line, "ver:"):
+ pkg.Version = strings.TrimSpace(strings.TrimPrefix(line, "ver:"))
+
+ case strings.HasPrefix(line, "desc:"):
+ pkg.Desc = strings.TrimSpace(strings.TrimPrefix(line, "desc:"))
+
+ case strings.HasPrefix(line, "deps:"):
+ pkg.Depends = append(pkg.Depends,
+ strings.Fields(strings.TrimSpace(strings.TrimPrefix(line, "deps:")))...)
+
+ case strings.HasPrefix(line, "conflicts:"):
+ pkg.Conflicts = append(pkg.Conflicts,
+ strings.Fields(strings.TrimSpace(strings.TrimPrefix(line, "conflicts:")))...)
+
+ case strings.HasPrefix(line, "type:"):
+ pkg.Type = strings.TrimSpace(strings.TrimPrefix(line, "type:"))
+
+ case strings.HasPrefix(line, "url:"):
+ pkg.URL = strings.TrimSpace(strings.TrimPrefix(line, "url:"))
+
+ case strings.HasPrefix(line, "after:"):
+ pkg.After = strings.TrimSpace(strings.TrimPrefix(line, "after:"))
+
+ case strings.HasPrefix(line, "remove:"):
+ pkg.Remove = strings.TrimSpace(strings.TrimPrefix(line, "remove:"))
+
+ case strings.HasPrefix(line, "message:"):
+ pkg.Message = strings.TrimSpace(strings.TrimPrefix(line, "message:"))
+ }
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, err
+ }
+
+ pkg.Repo = repo
+
+ subst := func(s string) string {
+ if repoURL != "" {
+ s = strings.ReplaceAll(s, "{URL}", repoURL)
}
+ s = strings.ReplaceAll(s, "{ARCH}", runtime.GOARCH)
+ s = strings.ReplaceAll(s, "{REPO}", pkg.Repo)
+ s = strings.ReplaceAll(s, "{NAME}", pkg.Name)
+ s = strings.ReplaceAll(s, "{VER}", pkg.Version)
+ return s
+ }
+
+ pkg.Name = subst(pkg.Name)
+ pkg.Version = subst(pkg.Version)
+ pkg.Desc = subst(pkg.Desc)
+ pkg.URL = subst(pkg.URL)
+ pkg.Type = subst(pkg.Type)
+ pkg.After = subst(pkg.After)
+ pkg.Remove = subst(pkg.Remove)
+ pkg.Message = subst(pkg.Message)
+ for i, dep := range pkg.Depends {
+ pkg.Depends[i] = subst(dep)
}
- return depends, taip, url, afterScript, removeScript
+ for i, conflict := range pkg.Conflicts {
+ pkg.Conflicts[i] = subst(conflict)
+ }
+
+ return pkg, nil
}
func GetPackages(destDir string) []Package {
var found []Package
- entries, _ := os.ReadDir(filepath.Join(destDir, paths.ReposDir))
- for _, e := range entries {
- if !e.IsDir() {
+
+ reposPath := filepath.Join(destDir, paths.ReposDir)
+
+ repoURLs := map[string]string{}
+ for _, repo := range database.GetRepos(destDir) {
+ repoURLs[repo.Name] = repo.URL
+ }
+
+ repos, err := os.ReadDir(reposPath)
+ if err != nil {
+ return found
+ }
+
+ for _, repo := range repos {
+ repoPath := filepath.Join(
+ reposPath,
+ repo.Name(),
+ )
+ repoInfo, err := os.Stat(repoPath)
+ if err != nil || !repoInfo.IsDir() {
continue
}
- data, err := os.ReadFile(filepath.Join(destDir, paths.ReposDir, e.Name(), "pkgs"))
+
+ packagesPath := filepath.Join(
+ repoPath,
+ "packages",
+ )
+
+ packages, err := os.ReadDir(packagesPath)
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})
+
+ for _, pkgDir := range packages {
+ pkgPath := filepath.Join(
+ packagesPath,
+ pkgDir.Name(),
+ )
+ pkgInfo, err := os.Stat(pkgPath)
+ if err != nil || !pkgInfo.IsDir() {
+ continue
+ }
+
+ pkg, err := readPackage(repo.Name(), pkgPath, repoURLs[repo.Name()])
+ if err != nil {
+ continue
+ }
+
+ if pkg.Name == "" {
+ pkg.Name = pkgDir.Name()
+ }
+
+ found = append(found, *pkg)
}
}
+
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 IsBuilt(pkg *Package, destDir string) bool {
+ builtDir := filepath.Join(
+ destDir,
+ paths.BuiltDir,
+ runtime.GOARCH,
+ pkg.Repo,
+ )
+ if _, err := os.Stat(
+ filepath.Join(builtDir, pkg.Name+".tar.zst"),
+ ); err == nil {
+ return true
+ }
+ if _, err := os.Stat(
+ filepath.Join(builtDir, pkg.Name+".qpmp"),
+ ); err == nil {
+ return true
+ }
+ return false
+}
+
+var versionRe = regexp.MustCompile(`[0-9]+|[a-zA-Z]+`)
+
+func trimTrailingZeros(segs []string) []string {
+ for len(segs) > 0 {
+ last := segs[len(segs)-1]
+ if n, err := strconv.Atoi(last); err == nil && n == 0 {
+ segs = segs[:len(segs)-1]
+ } else {
+ break
+ }
+ }
+ return segs
+}
+
+func versionCompare(a, b string) int {
+ aa := trimTrailingZeros(versionRe.FindAllString(a, -1))
+ bb := trimTrailingZeros(versionRe.FindAllString(b, -1))
+
+ n := len(aa)
+ if len(bb) < n {
+ n = len(bb)
+ }
+
+ for i := 0; i < n; i++ {
+ x, y := aa[i], bb[i]
+
+ xn, xerr := strconv.Atoi(x)
+ yn, yerr := strconv.Atoi(y)
+ if xerr == nil && yerr == nil {
+ if xn != yn {
+ if xn > yn {
+ return 1
+ }
+ return -1
+ }
+ continue
+ }
+
+ if x != y {
+ if x > y {
+ return 1
+ }
+ return -1
+ }
+ }
+
+ if len(aa) < len(bb) {
+ return -1
+ }
+ if len(aa) > len(bb) {
+ return 1
+ }
+ return 0
+}
+
+func isStale(pkg *Package, destDir string) bool {
+ builtDir := filepath.Join(
+ destDir,
+ paths.BuiltDir,
+ runtime.GOARCH,
+ pkg.Repo,
+ )
+
+ artifact := filepath.Join(builtDir, pkg.Name+".tar.zst")
+ if _, err := os.Stat(artifact); err != nil {
+ artifact = filepath.Join(builtDir, pkg.Name+".qpmp")
+ if _, err := os.Stat(artifact); err != nil {
+ return false
+ }
+ }
+
+ artInfo, err := os.Stat(artifact)
+ if err != nil {
+ return false
+ }
+
+ for _, src := range []string{"manifest", "build.sh"} {
+ info, err := os.Stat(filepath.Join(pkg.Path, src))
+ if err == nil && info.ModTime().After(artInfo.ModTime()) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func isOldInstalled(key string, destDir string) bool {
+ pkg, err := getPackage(key, destDir)
+ if err != nil {
+ return false
+ }
+ ver := database.GetPackageVersion(key, destDir)
+ return ver != "" && versionCompare(pkg.Version, ver) > 0
+}
+
+func buildable(p *Package) bool {
+ return p.Type == "archive" || p.Type == "binary"
+}
+
+func Expand(cmd string, oths []string, destDir string) ([]string, error) {
+ if cmd == "o" && len(oths) == 0 {
+ oths = []string{"@old"}
+ }
+
+ var out []string
+ seen := map[string]bool{}
+
+ add := func(name string) {
+ if !seen[name] {
+ seen[name] = true
+ out = append(out, name)
+ }
+ }
+
+ for _, arg := range oths {
+ lower := strings.ToLower(arg)
+
+ var repo, kind string
+ if strings.Contains(lower, "/") {
+ parts := strings.SplitN(lower, "/", 2)
+ repo = parts[0]
+ kind = parts[1]
+ } else {
+ kind = lower
+ }
+
+ switch kind {
+ case "@all", "@inst", "@notinst", "@old":
+ default:
+ add(arg)
+ continue
+ }
+
+ packages := GetPackages(destDir)
+
+ switch cmd {
+ case "-":
+ for key := range database.ReadInstalled(destDir) {
+ ks, ok := key.(string)
+ if !ok || (repo != "" && !strings.HasPrefix(ks, repo+"/")) {
+ continue
+ }
+
+ switch kind {
+ case "@all", "@inst":
+ add(ks)
+ case "@old":
+ if isOldInstalled(ks, destDir) {
+ add(ks)
+ }
+ }
+ }
+
+ case "b":
+ for i := range packages {
+ p := &packages[i]
+ if !buildable(p) {
+ continue
+ }
+ if repo != "" && strings.ToLower(p.Repo) != repo {
+ continue
+ }
+
+ key := p.Repo + "/" + p.Name
+
+ switch kind {
+ case "@all":
+ add(key)
+ case "@inst":
+ if IsBuilt(p, destDir) {
+ add(key)
+ }
+ case "@notinst":
+ if !IsBuilt(p, destDir) {
+ add(key)
+ }
+ case "@old":
+ if IsBuilt(p, destDir) && isStale(p, destDir) {
+ add(key)
+ }
+ }
+ }
+
+ default: // "+" and "o"
+ for i := range packages {
+ p := &packages[i]
+ if repo != "" && strings.ToLower(p.Repo) != repo {
+ continue
+ }
+
+ key := p.Repo + "/" + p.Name
+ installed := database.IsInstalled(key, destDir)
+
+ switch kind {
+ case "@all":
+ add(key)
+ case "@inst":
+ if installed {
+ add(key)
+ }
+ case "@notinst":
+ if !installed {
+ add(key)
+ }
+ case "@old":
+ if installed {
+ ver := database.GetPackageVersion(key, destDir)
+ if versionCompare(p.Version, ver) > 0 {
+ add(key)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ slices.Sort(out)
+ return out, nil
+}
+
func FindPackages(name string, destDir string) []Package {
var found []Package
- var packages = GetPackages(destDir)
- var splitName = false
- var repo string
+ name = strings.ToLower(name)
+
+ 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)
- }
+ for _, pkg := range GetPackages(destDir) {
+ if repo != "" && pkg.Repo != repo {
+ continue
+ }
+
+ if strings.Contains(
+ strings.ToLower(pkg.Name),
+ name,
+ ) {
+ found = append(found, pkg)
}
}
+
return found
}
func getPackage(name string, destDir string) (*Package, error) {
- var pkgs []Package
- var pkg Package
+ name = strings.ToLower(name)
+
+ var repo, pkgName string
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])
+ repo = parts[0]
+ pkgName = parts[1]
+ } else {
+ pkgName = name
+ }
+
+ var exact []Package
+ for _, p := range GetPackages(destDir) {
+ if strings.ToLower(p.Name) != pkgName {
+ continue
+ }
+ if repo != "" && strings.ToLower(p.Repo) != repo {
+ continue
+ }
+ exact = append(exact, p)
+ }
+
+ if len(exact) == 0 {
+ return nil, errors.New("no packages found with name " + name)
+ }
+
+ if len(exact) == 1 {
+ return &exact[0], nil
+ }
+
+ fmt.Println("found multiple packages:")
+
+ for _, pkg := range exact {
+ fmt.Printf(
+ "%s/%s - %s\n",
+ pkg.Repo,
+ pkg.Name,
+ pkg.Version,
+ )
+ }
+
+ return nil, errors.New(
+ "multiple packages found, specify repository/name",
+ )
+}
+
+func manifestString(p *Package) string {
+ var b strings.Builder
+ b.WriteString("repo: " + p.Repo + "\n")
+ b.WriteString("name: " + p.Name + "\n")
+ b.WriteString("ver: " + p.Version + "\n")
+ if p.Desc != "" {
+ b.WriteString("desc: " + p.Desc + "\n")
+ }
+ if len(p.Depends) > 0 {
+ b.WriteString("deps: " + strings.Join(p.Depends, " ") + "\n")
+ }
+ if len(p.Conflicts) > 0 {
+ b.WriteString("conflicts: " + strings.Join(p.Conflicts, " ") + "\n")
+ }
+ if p.Type != "" {
+ b.WriteString("type: " + p.Type + "\n")
+ }
+ if p.URL != "" {
+ b.WriteString("url: " + p.URL + "\n")
+ }
+ return b.String()
+}
+
+func tarZstd(payloadDir, destPath, manifest string) error {
+ out, err := os.Create(destPath)
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+
+ zw, err := zstd.NewWriter(out)
+ if err != nil {
+ return err
+ }
+
+ tw := tar.NewWriter(zw)
+
+ hdr := &tar.Header{
+ Name: "manifest",
+ Mode: 0644,
+ Size: int64(len(manifest)),
+ }
+ if err := tw.WriteHeader(hdr); err != nil {
+ return err
+ }
+ if _, err := tw.Write([]byte(manifest)); err != nil {
+ return err
+ }
+
+ if err := writeTarTree(payloadDir, tw, "payload"); err != nil {
+ return err
+ }
+
+ if err := tw.Close(); err != nil {
+ return err
+ }
+ return zw.Close()
+}
+
+func tarPayload(payloadDir, destPath string) error {
+ out, err := os.Create(destPath)
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+
+ zw, err := zstd.NewWriter(out)
+ if err != nil {
+ return err
+ }
+
+ tw := tar.NewWriter(zw)
+
+ if err := writeTarTree(payloadDir, tw, ""); err != nil {
+ return err
+ }
+
+ if err := tw.Close(); err != nil {
+ return err
+ }
+ return zw.Close()
+}
+
+func writeTarTree(payloadDir string, tw *tar.Writer, prefix string) error {
+ return filepath.Walk(payloadDir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ rel, err := filepath.Rel(payloadDir, path)
+ if err != nil {
+ return err
+ }
+
+ name := filepath.Join(prefix, filepath.ToSlash(rel))
+
+ switch {
+ case info.Mode()&os.ModeSymlink != 0:
+ link, err := os.Readlink(path)
+ if err != nil {
+ return err
+ }
+ return tw.WriteHeader(&tar.Header{
+ Name: name,
+ Mode: int64(info.Mode()),
+ Typeflag: tar.TypeSymlink,
+ Linkname: link,
+ })
+
+ case info.IsDir():
+ return tw.WriteHeader(&tar.Header{
+ Name: name + "/",
+ Mode: int64(info.Mode()),
+ Typeflag: tar.TypeDir,
+ })
+
+ default:
+ hdr := &tar.Header{
+ Name: name,
+ Mode: int64(info.Mode()),
+ Size: info.Size(),
+ ModTime: info.ModTime(),
+ }
+ if err := tw.WriteHeader(hdr); err != nil {
+ return err
+ }
+ f, err := os.Open(path)
+ if err != nil {
+ return err
}
- return nil, errors.New("? found multiple packages with name " + name + " in " + repo + " repository")
+ _, err = io.Copy(tw, f)
+ f.Close()
+ return err
}
- } 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])
+ })
+}
+
+func readBuildConfig(destDir string) map[string]string {
+ cfg := map[string]string{}
+ cfgPath := filepath.Join(destDir, paths.BuildConfigFile)
+ data, err := os.ReadFile(cfgPath)
+ if err != nil {
+ return cfg
+ }
+ m := uzbekdb.Loads(string(data), "map")
+ if m == nil {
+ return cfg
+ }
+ mp, ok := m.(map[interface{}][]interface{})
+ if !ok {
+ return cfg
+ }
+ for k, vals := range mp {
+ if len(vals) > 0 {
+ cfg[fmt.Sprintf("%v", k)] = fmt.Sprintf("%v", vals[0])
+ }
+ }
+ return cfg
+}
+
+func buildPackage(pkg *Package, destDir string) (string, error) {
+ buildScript := filepath.Join(pkg.Path, "build.sh")
+ if _, err := os.Stat(buildScript); err != nil {
+ return "", errors.New(
+ "package has no build.sh",
+ )
+ }
+
+ payloadDir := filepath.Join(
+ destDir,
+ "tmp",
+ "build_"+pkg.Repo+"_"+pkg.Name,
+ )
+ os.RemoveAll(payloadDir)
+ os.MkdirAll(payloadDir, 0755)
+
+ env := append([]string{}, os.Environ()...)
+ for k, v := range readBuildConfig(destDir) {
+ env = append(env, k+"="+v)
+ }
+ env = append(
+ env,
+ "DESTDIR="+payloadDir,
+ "PKGURL="+pkg.URL,
+ "ARCH="+runtime.GOARCH,
+ "PKGREPO="+pkg.Repo,
+ "PKGVER="+pkg.Version,
+ "PKGNAME="+pkg.Name,
+ )
+ if v := os.Getenv("MAKEOPTS"); v != "" {
+ env = append(env, "MAKEOPTS="+v)
+ }
+
+ cmd := exec.Command("sh", "build.sh")
+ cmd.Dir = pkg.Path
+ cmd.Env = env
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ if err := cmd.Run(); err != nil {
+ return "", err
+ }
+
+ entries, err := os.ReadDir(payloadDir)
+ if err != nil {
+ return "", err
+ }
+ if len(entries) == 0 {
+ return "", errors.New(
+ "build.sh produced no files",
+ )
+ }
+
+ return payloadDir, nil
+}
+
+func isSafePath(p string) bool {
+ p = filepath.ToSlash(p)
+ if p == "" || p == "/" {
+ return false
+ }
+ for _, part := range strings.Split(p, "/") {
+ if part == "." || part == ".." {
+ return false
+ }
+ }
+ return true
+}
+
+func conflictCheck(self string, conflicts []string, destDir string) (string, bool) {
+ for _, c := range conflicts {
+ if strings.Contains(c, "/") {
+ if c == self {
+ continue
}
- return nil, errors.New("found multiple packages with name " + name + " in repositories")
- } else {
- pkg = pkgs[0]
+ if database.IsInstalled(c, destDir) || installing[c] {
+ return c, true
+ }
+ continue
+ }
+
+ for key := range database.ReadInstalled(destDir) {
+ ks := fmt.Sprintf("%v", key)
+ if ks != self && strings.HasSuffix(ks, "/"+c) {
+ return ks, true
+ }
+ }
+ for k := range installing {
+ if k != self && strings.HasSuffix(k, "/"+c) {
+ return k, true
+ }
+ }
+ }
+ return "", false
+}
+
+func installPayload(payloadDir, destDir string, pkg *Package, record bool) error {
+ err := helpers.CopyDir(payloadDir, destDir)
+ if err != nil {
+ return fmt.Errorf(
+ "copy failed: %w",
+ err,
+ )
+ }
+
+ if !record {
+ return nil
+ }
+
+ outputFilePath := filepath.Join(
+ destDir,
+ paths.FilesDir,
+ pkg.Repo+"/"+pkg.Name+".files",
+ )
+ os.MkdirAll(filepath.Dir(outputFilePath), 0755)
+
+ outputFile, err := os.Create(outputFilePath)
+ if err != nil {
+ return err
+ }
+ defer outputFile.Close()
+
+ err = filepath.WalkDir(
+ payloadDir,
+ func(path string, d os.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ if d.IsDir() {
+ return nil
+ }
+ rel, err := filepath.Rel(payloadDir, path)
+ if err != nil {
+ return err
+ }
+ if !isSafePath(rel) {
+ return errors.New(
+ "unsafe path in payload: " + rel,
+ )
+ }
+ _, err = outputFile.WriteString(
+ "/"+filepath.ToSlash(rel)+"\n",
+ )
+ return err
+ },
+ )
+ return err
+}
+
+func qpmpManifest(qpmpPath string) (string, error) {
+ f, err := os.Open(qpmpPath)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+
+ zr, err := zstd.NewReader(f)
+ if err != nil {
+ return "", err
+ }
+ defer zr.Close()
+
+ tr := tar.NewReader(zr)
+ for {
+ hdr, err := tr.Next()
+ if err != nil {
+ return "", err
+ }
+ if hdr.Name == "manifest" {
+ data, err := io.ReadAll(tr)
+ if err != nil {
+ return "", err
+ }
+ return string(data), nil
+ }
+ }
+}
+
+func qpmpVersion(qpmpPath string) (string, error) {
+ data, err := qpmpManifest(qpmpPath)
+ if err != nil {
+ return "", err
+ }
+ for _, line := range strings.Split(data, "\n") {
+ line = strings.TrimSpace(line)
+ if strings.HasPrefix(line, "ver:") {
+ return strings.TrimSpace(strings.TrimPrefix(line, "ver:")), nil
+ }
+ }
+ return "", errors.New("no ver in qpmp manifest")
+}
+
+func Build(name string, args []string, destDir string) error {
+ pkg, err := getPackage(name, destDir)
+ if err != nil {
+ return err
+ }
+
+ if pkg.Type != "archive" && pkg.Type != "binary" {
+ return errors.New(
+ "cannot build package of type " + pkg.Type,
+ )
+ }
+
+ if _, err := os.Stat(filepath.Join(pkg.Path, "build.sh")); err != nil {
+ return errors.New(
+ pkg.Repo + "/" + pkg.Name + " has no build.sh",
+ )
+ }
+
+ fmt.Println(
+ ":: running build.sh for",
+ pkg.Repo+"/"+pkg.Name,
+ )
+
+ payloadDir, err := buildPackage(pkg, destDir)
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(payloadDir)
+
+ builtDir := filepath.Join(
+ destDir,
+ paths.BuiltDir,
+ runtime.GOARCH,
+ pkg.Repo,
+ )
+ os.MkdirAll(builtDir, 0755)
+
+ if slices.Contains(args, ":qpmp") {
+ qpmpPath := filepath.Join(builtDir, pkg.Name+".qpmp")
+
+ err = tarZstd(
+ payloadDir,
+ qpmpPath,
+ manifestString(pkg),
+ )
+ if err != nil {
+ return err
}
+
+ fmt.Println(
+ ":: built",
+ pkg.Repo+"/"+pkg.Name,
+ "->",
+ qpmpPath,
+ )
+
+ return nil
+ }
+
+ tarPath := filepath.Join(builtDir, pkg.Name+".tar.zst")
+
+ err = tarPayload(payloadDir, tarPath)
+ if err != nil {
+ return err
+ }
+
+ fmt.Println(
+ ":: built",
+ pkg.Repo+"/"+pkg.Name,
+ "->",
+ tarPath,
+ )
+
+ return nil
+}
+
+func installQpmp(qpmpPath string, args []string, destDir string) error {
+ stagingDir := filepath.Join(
+ destDir,
+ "tmp",
+ "qpmp_"+filepath.Base(qpmpPath),
+ )
+ os.RemoveAll(stagingDir)
+ os.MkdirAll(stagingDir, 0755)
+ defer os.RemoveAll(stagingDir)
+
+ file, err := os.Open(qpmpPath)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ err = extract.Zstd(
+ context.TODO(),
+ file,
+ stagingDir,
+ nil,
+ )
+ if err != nil {
+ return err
+ }
+
+ pkg, err := readPackage("", stagingDir, "")
+ if err != nil {
+ return err
+ }
+ if pkg.Name == "" {
+ pkg.Name = strings.TrimSuffix(
+ filepath.Base(qpmpPath),
+ ".qpmp",
+ )
+ }
+ if pkg.Repo == "" {
+ pkg.Repo = "local"
+ }
+ pkg.Path = stagingDir
+
+ key := pkg.Repo + "/" + pkg.Name
+
+ noLog := slices.Contains(args, ":nl")
+
+ if slices.Contains(args, ":a") {
+ if !helpers.Ask(
+ "install " + key +
+ " with version " +
+ pkg.Version + "?",
+ ) {
+ return errors.New("canceled by user")
+ }
+ }
+
+ payloadDir := filepath.Join(stagingDir, "payload")
+ if _, err := os.Stat(payloadDir); err != nil {
+ return errors.New(
+ "qpmp has no payload",
+ )
+ }
+
+ fmt.Println(
+ ":: installing " +
+ key,
+ )
+
+ err = helpers.CopyDir(payloadDir, destDir)
+ if err != nil {
+ return fmt.Errorf(
+ "copy failed: %w",
+ err,
+ )
+ }
+
+ filesPath := filepath.Join(
+ destDir,
+ paths.FilesDir,
+ key+".files",
+ )
+ if noLog {
+ fmt.Println(
+ ":: " + key +
+ " (" + pkg.Version + ") installed",
+ )
+
+ return nil
+ }
+
+ os.MkdirAll(filepath.Dir(filesPath), 0755)
+
+ outputFile, err := os.Create(filesPath)
+ if err != nil {
+ return err
}
- return &pkg, nil
+ defer outputFile.Close()
+
+ err = filepath.WalkDir(
+ payloadDir,
+ func(path string, d os.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ if d.IsDir() {
+ return nil
+ }
+ rel, err := filepath.Rel(payloadDir, path)
+ if err != nil {
+ return err
+ }
+ if !isSafePath(rel) {
+ return errors.New(
+ "unsafe path in qpmp payload: " + rel,
+ )
+ }
+ _, err = outputFile.WriteString(
+ "/"+filepath.ToSlash(rel)+"\n",
+ )
+ return err
+ },
+ )
+ if err != nil {
+ return err
+ }
+ outputFile.Close()
+
+ err = database.RegPackage(
+ key,
+ pkg.Version,
+ destDir,
+ )
+ if err != nil {
+ return err
+ }
+
+ fmt.Println(
+ ":: " + key +
+ " (" + pkg.Version + ") installed",
+ )
+
+ return nil
}
func Install(name string, args []string, destDir string) error {
- // verbose := slices.Contains(args, ":v")
+ if strings.HasSuffix(name, ".qpmp") {
+ if _, err := os.Stat(name); err == nil {
+ return installQpmp(name, args, destDir)
+ }
+ }
+
reinstall := slices.Contains(args, ":r")
disableDeps := slices.Contains(args, ":nd")
+ noLog := slices.Contains(args, ":nl")
+ force := slices.Contains(args, ":f")
+
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 _, ok := installing[key]; ok {
+ return nil
+ }
+
+ installing[key] = true
+ defer delete(installing, key)
- key := pkg.Repo + "/" + pkg.Name
+ if !force {
+ if matched, ok := conflictCheck(key, pkg.Conflicts, destDir); ok {
+ return errors.New(
+ key + " conflicts with " + matched,
+ )
+ }
+ }
- 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)
+ if len(pkg.Depends) > 0 {
+ fmt.Println(
+ helpers.ToString(len(pkg.Depends)) +
+ " deps for " +
+ key + ":",
+ )
+
+ for i, dep := range pkg.Depends {
+ if dep == pkg.Name || dep == key {
+ return errors.New(
+ "broken deps in package " + dep,
+ )
}
- depPkgs := FindPackages(n, destDir)
- v := "not found"
+ depPkgs := FindPackages(dep, destDir)
+
+ version := "not found"
if len(depPkgs) > 0 {
- v = fmt.Sprintf("%v", depPkgs[0].Data[1])
+ version = depPkgs[0].Version
}
- fmt.Printf("%v. %s - %v\n", i+1, n, v)
+ fmt.Printf(
+ "%d. %s - %s\n",
+ i+1,
+ dep,
+ version,
+ )
}
}
}
-
+
if slices.Contains(args, ":a") {
- if !helpers.Ask("install " + pkg.Repo + "/" + pkg.Name + " with version " + helpers.ToString(pkg.Data[1])) {
+ if !helpers.Ask(
+ "install " +
+ key +
+ " with version " +
+ pkg.Version,
+ ) {
return errors.New("canceled by user")
}
}
-
+
if !disableDeps {
- for _, dep := range depends {
+ for _, dep := range pkg.Depends {
depPkg, err := getPackage(dep, destDir)
if err != nil {
return err
@@ -228,204 +1194,489 @@ func Install(name string, args []string, destDir string) error {
}
}
- fmt.Println(":: installing " + pkg.Name + " from repo " + pkg.Repo)
+ fmt.Println(
+ ":: installing " +
+ key,
+ )
+
+ builtQpmp := filepath.Join(
+ destDir,
+ paths.BuiltDir,
+ runtime.GOARCH,
+ pkg.Repo,
+ pkg.Name+".qpmp",
+ )
+ if _, err := os.Stat(builtQpmp); err == nil {
+ if ver, err := qpmpVersion(builtQpmp); err == nil && ver == pkg.Version {
+ fmt.Println(
+ ":: found built",
+ key,
+ ver,
+ "-> installing from .qpmp",
+ )
+ return installQpmp(builtQpmp, args, destDir)
+ }
+ }
+
+ if pkg.Type == "archive" {
+ destArchivePath := filepath.Join(
+ destDir,
+ "tmp",
+ pkg.Name+"_"+pkg.Repo+".tar.zst",
+ )
- if taip == "arch" {
- destArchivePath := filepath.Join(destDir, "tmp", pkg.Name + pkg.Repo + ".tar.zst")
- fullDestPath := filepath.Join(pkgPath, "arch")
+ fullDestPath := filepath.Join(
+ pkg.Path,
+ "extract",
+ )
- os.MkdirAll(fullDestPath, 0755)
os.MkdirAll(filepath.Dir(destArchivePath), 0755)
-
- client := &http.Client{
- Timeout: 10 * time.Second,
- }
+ defer os.Remove(destArchivePath)
+ defer os.RemoveAll(fullDestPath)
- resp, err := client.Get(url)
- if err != nil {
- fmt.Println("!! failed to download", url, err)
- return err
- }
- defer resp.Body.Close()
+ client := helpers.HTTPClient()
- f, err := os.Create(destArchivePath)
- if err != nil {
- fmt.Println("!! failed to create file", err)
+ download := func() error {
+ resp, err := client.Get(pkg.URL)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return fmt.Errorf(
+ "download failed: HTTP %d",
+ resp.StatusCode,
+ )
+ }
+
+ f, err := os.Create(destArchivePath)
+ if err != nil {
+ return err
+ }
+ _, err = io.Copy(f, resp.Body)
+ f.Close()
return err
}
- defer f.Close()
- _, err = io.Copy(f, resp.Body)
- f.Close()
+ err := download()
if err != nil {
- fmt.Println("!! failed to copy", err)
- return err
+ fmt.Println(
+ "!! failed to download",
+ pkg.URL,
+ "-> building from source",
+ )
+ payloadDir, berr := buildPackage(pkg, destDir)
+ if berr != nil {
+ return fmt.Errorf(
+ "download failed: %w; build fallback failed: %v",
+ err,
+ berr,
+ )
+ }
+ defer os.RemoveAll(payloadDir)
+ fullDestPath = payloadDir
+ } else {
+ fmt.Println(
+ ":: downloaded",
+ pkg.URL,
+ )
+
+ os.RemoveAll(fullDestPath)
+
+ file, err := os.Open(destArchivePath)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ err = extract.Zstd(
+ context.TODO(),
+ file,
+ fullDestPath,
+ nil,
+ )
+ if err != nil {
+ return err
+ }
}
- fmt.Println(":: downloaded", url, "to", destArchivePath)
+ fmt.Println(":: copying files to /")
- _ = os.RemoveAll(fullDestPath)
-
- file, err := os.Open(destArchivePath)
+ err = installPayload(fullDestPath, destDir, pkg, !noLog)
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)
+ } else if pkg.Type == "script" {
+ cmd := exec.Command("sh", "install.sh")
+
+ cmd.Dir = pkg.Path
+ cmd.Env = append(
+ os.Environ(),
+ "DESTDIR="+destDir,
+ )
+
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ err := cmd.Run()
if err != nil {
- fmt.Println("!! failed to extract", err)
return err
}
- fmt.Println(":: removing govnofiles from package...")
+ } else if pkg.Type == "binary" {
+ outPath := filepath.Join(
+ destDir,
+ "usr/bin",
+ pkg.Name,
+ )
+
+ os.MkdirAll(filepath.Dir(outPath), 0755)
- _ = os.Remove(filepath.Join(fullDestPath, ".BUILDINFO"))
- _ = os.Remove(filepath.Join(fullDestPath, ".MTREE"))
- _ = os.Remove(filepath.Join(fullDestPath, ".PKGINFO"))
- _ = os.Remove(filepath.Join(fullDestPath, ".INSTALL"))
+ client := helpers.HTTPClient()
- fmt.Println(":: copying files to / ...")
-
- err = helpers.CopyDir(fullDestPath, filepath.Join(destDir, "/"))
+ resp, err := client.Get(pkg.URL)
+ downloadErr := error(nil)
if err != nil {
- fmt.Println("!! failed to copy files to /:", err)
+ downloadErr = err
+ } else if resp.StatusCode != http.StatusOK {
+ downloadErr = fmt.Errorf(
+ "download failed: HTTP %d",
+ resp.StatusCode,
+ )
} else {
- fmt.Println(":: successfully copied to /")
+ f, cerr := os.Create(outPath)
+ if cerr != nil {
+ downloadErr = cerr
+ } else {
+ _, cerr = io.Copy(f, resp.Body)
+ f.Close()
+ if cerr != nil {
+ downloadErr = cerr
+ } else if cerr = os.Chmod(outPath, 0755); cerr != nil {
+ downloadErr = cerr
+ }
+ }
}
-
- outputFilePath := filepath.Join(pkgPath, "files")
- outputFile, err := os.Create(outputFilePath)
- if err != nil {
- fmt.Println("!! failed to create files list:", err)
- return err
+ if resp != nil {
+ resp.Body.Close()
}
- 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
+ if downloadErr != nil {
+ fmt.Println(
+ "!! failed to download",
+ pkg.URL,
+ "-> building from source",
+ )
+ payloadDir, berr := buildPackage(pkg, destDir)
+ if berr != nil {
+ return fmt.Errorf(
+ "download failed: %w; build fallback failed: %v",
+ downloadErr,
+ berr,
+ )
}
+ defer os.RemoveAll(payloadDir)
- relPath, err := filepath.Rel(fullDestPath, path)
- if err != nil {
+ fmt.Println(":: copying files to /")
+
+ if err := installPayload(payloadDir, destDir, pkg, !noLog); err != nil {
return err
}
+ } else {
+ if !noLog {
+ outputFilePath := filepath.Join(
+ destDir,
+ paths.FilesDir,
+ pkg.Repo+"/"+pkg.Name+".files",
+ )
+ os.MkdirAll(filepath.Dir(outputFilePath), 0755)
- formattedPath := "/" + filepath.ToSlash(relPath)
+ outputFile, err := os.Create(outputFilePath)
+ if err != nil {
+ return err
+ }
+ defer outputFile.Close()
- _, err = outputFile.WriteString(formattedPath + "\n")
- return err
- })
+ _, err = outputFile.WriteString(
+ "/usr/bin/" + pkg.Name + "\n",
+ )
- if err != nil {
- fmt.Println("!! failed to index files:", err)
- return err
+ if err != nil {
+ return err
+ }
+ }
}
- } else if taip == "script" {
- cmd := exec.Command("sh", "install.sh")
- cmd.Dir = pkgPath
- cmd.Env = append(os.Environ(), "DESTDIR="+destDir)
+
+ } else {
+ return errors.New(
+ "unknown package type: " + pkg.Type,
+ )
+ }
+
+
+ if pkg.After != "" {
+ fmt.Println(
+ ":: running after script",
+ pkg.After,
+ )
+
+ cmd := exec.Command(
+ "sh",
+ pkg.After,
+ )
+
+ cmd.Dir = pkg.Path
+ 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 !noLog {
+ err = database.RegPackage(
+ pkg.Repo+"/"+pkg.Name,
+ pkg.Version,
+ destDir,
+ )
+
if err != nil {
- return err
+ return errors.New(
+ "failed to register package: " +
+ err.Error(),
+ )
}
- defer resp.Body.Close()
-
- outPath := filepath.Join(destDir, "/usr/bin", pkg.Name)
-
- f, err := os.Create(outPath)
+ }
+
+ if pkg.Message != "" {
+ dataMsg, err := os.ReadFile(
+ filepath.Join(pkg.Path, pkg.Message),
+ )
if err != nil {
- return err
+ return errors.New(
+ "failed to read message from package: " +
+ err.Error(),
+ )
}
-
- _, err = io.Copy(f, resp.Body)
- f.Close()
- if err != nil {
- return err
+ fmt.Println(
+ ":: message from "+pkg.Repo+"/"+pkg.Name+
+ " "+pkg.Version+":\n"+
+ string(dataMsg),
+ )
+ }
+
+ fmt.Println(
+ ":: "+pkg.Repo+"/"+pkg.Name+
+ " ("+pkg.Version+") installed",
+ )
+
+ return nil
+}
+
+func removeLocal(name string, args []string, destDir string) error {
+ key := name
+ if !strings.Contains(key, "/") {
+ for k := range database.ReadInstalled(destDir) {
+ if kStr, ok := k.(string); ok {
+ if strings.HasSuffix(kStr, "/"+name) {
+ key = kStr
+ break
+ }
+ }
}
-
- err = os.Chmod(outPath, 0755)
- if err != nil {
- return err
+ }
+
+ if !database.IsInstalled(key, destDir) {
+ return errors.New(
+ "no packages found with name " + name,
+ )
+ }
+
+ if slices.Contains(args, ":a") {
+ if !helpers.Ask(
+ "remove " + key + "?",
+ ) {
+ return errors.New(
+ "canceled by user",
+ )
}
- } 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()
+ filesPath := filepath.Join(
+ destDir,
+ paths.FilesDir,
+ key+".files",
+ )
+ if _, err := os.Stat(filesPath); err != nil {
+ return errors.New(
+ "no files list for " + key,
+ )
+ }
+
+ f, err := os.Open(filesPath)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ scanner := bufio.NewScanner(f)
+
+ for scanner.Scan() {
+ file := scanner.Text()
+
+ if file == "" {
+ continue
+ }
+
+ if !isSafePath(file) {
+ fmt.Fprintf(
+ os.Stderr,
+ "w! skipping unsafe path: %s\n",
+ file,
+ )
+ continue
+ }
+
+ path := filepath.Join(destDir, file)
+
+ if _, err := os.Stat(path); err == nil {
+ fmt.Println(
+ "removing",
+ path,
+ )
+ if err := os.RemoveAll(path); err != nil {
+ fmt.Fprintf(
+ os.Stderr,
+ "failed removing %s: %v\n",
+ path,
+ err,
+ )
+ }
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return err
+ }
+
+ if !slices.Contains(args, ":nl") {
+ err = database.UnRegPackage(
+ key,
+ destDir,
+ )
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")
+ os.Remove(filesPath)
+
+ fmt.Println(
+ ":: " + key + " removed",
+ )
+
return nil
}
func Remove(name string, args []string, destDir string) error {
pkg, err := getPackage(name, destDir)
if err != nil {
+ if strings.HasPrefix(
+ err.Error(),
+ "no packages found",
+ ) {
+ return removeLocal(name, args, destDir)
+ }
return err
}
+ key := pkg.Repo + "/" + pkg.Name
+
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")
+ localVersion := database.GetPackageVersion(
+ key,
+ destDir,
+ )
+
+ if !helpers.Ask(
+ "remove " + key +
+ " repo version " +
+ pkg.Version +
+ " local version " +
+ localVersion +
+ "?",
+ ) {
+ return errors.New(
+ "canceled by user",
+ )
}
}
- pkgPath := filepath.Join(destDir, paths.ReposDir, pkg.Repo, "packages", pkg.Name)
- _, _, _, _, removeScript := readPackage(pkgPath)
+ if pkg.Remove != "" {
+ fmt.Println(
+ ":: running remove script " +
+ pkg.Remove +
+ " ...",
+ )
+
+ cmd := exec.Command(
+ "sh",
+ pkg.Remove,
+ )
+
+ cmd.Dir = pkg.Path
+ cmd.Env = append(
+ os.Environ(),
+ "DESTDIR="+destDir,
+ )
- 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")
+
+ filesPath := filepath.Join(
+ destDir,
+ paths.FilesDir,
+ key+".files",
+ )
+ persistentFiles := true
+ if _, err := os.Stat(filesPath); err != nil {
+ filesPath = filepath.Join(
+ pkg.Path,
+ "files",
+ )
+ persistentFiles = false
+ }
+
+
+ if _, err := os.Stat(filesPath); err == nil {
+
+ f, err := os.Open(filesPath)
+ if err != nil {
+ return err
+ }
defer f.Close()
+
scanner := bufio.NewScanner(f)
+
for scanner.Scan() {
file := scanner.Text()
@@ -433,36 +1684,109 @@ func Remove(name string, args []string, destDir string) error {
continue
}
- path := filepath.Join(destDir, file)
+ if !isSafePath(file) {
+ fmt.Fprintf(
+ os.Stderr,
+ "w! skipping unsafe path: %s\n",
+ 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)
+ fmt.Println(
+ "removing",
+ path,
+ )
+
+ err := os.RemoveAll(path)
+ if err != nil {
+ fmt.Fprintf(
+ os.Stderr,
+ "failed removing %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)
+ return err
}
+
+ } else if pkg.Remove == "" {
+ return errors.New(
+ "package has no files list and no remove script",
+ )
}
- if removeScript == "" {
- if _, err := os.Stat("files"); os.IsNotExist(err) {
- return errors.New("failed to remove package file (no remove script and paths file)")
+
+ if !slices.Contains(args, ":nl") {
+ err = database.UnRegPackage(
+ key,
+ destDir,
+ )
+
+ if err != nil {
+ return err
}
}
-
- err = database.UnRegPackage(pkg.Repo+"/"+pkg.Name, destDir)
- if err != nil {
- return err
+
+ if persistentFiles {
+ os.Remove(filesPath)
+ }
+
+
+ fmt.Println(
+ ":: "+key+
+ " ("+pkg.Version+") removed",
+ )
+
+ return nil
+}
+
+func Upgrade(names []string, args []string, destDir string) error {
+ upgraded := 0
+
+ for _, n := range names {
+ pkg, err := getPackage(n, destDir)
+ if err != nil {
+ fmt.Println("w! skipping", n, err)
+ continue
+ }
+
+ key := pkg.Repo + "/" + pkg.Name
+
+ ver := database.GetPackageVersion(key, destDir)
+ if ver == "" {
+ continue
+ }
+
+ fmt.Println(
+ ":: upgrading", key,
+ ver, "->", pkg.Version,
+ )
+ if err := Install(key, args, destDir); err != nil {
+ return err
+ }
+ upgraded++
+ }
+
+ if upgraded == 0 {
+ fmt.Println(":: nothing to upgrade")
+ } else {
+ fmt.Println("::", upgraded, "package(s) upgraded")
}
- v := helpers.ToString(pkg.Data[1])
- fmt.Println(":: " + pkg.Repo + "/" + pkg.Name + " (" + v + ") removed")
return nil
}