diff --git a/assets/coverage-badge.svg b/assets/coverage-badge.svg
index ffdf7ae..e1e8356 100644
--- a/assets/coverage-badge.svg
+++ b/assets/coverage-badge.svg
@@ -11,7 +11,7 @@
coverage
coverage
- 19.4%
- 19.4%
+ 18.8%
+ 18.8%
diff --git a/e2e-tests/__snapshots__/TestE2EIssue129RepoTomlImportTest_issue-129-repo-toml-import-test_ubuntu-24.04_1.snap.stdout b/e2e-tests/__snapshots__/TestE2EIssue129RepoTomlImportTest_issue-129-repo-toml-import-test_ubuntu-24.04_1.snap.stdout
new file mode 100755
index 0000000..2a17b58
--- /dev/null
+++ b/e2e-tests/__snapshots__/TestE2EIssue129RepoTomlImportTest_issue-129-repo-toml-import-test_ubuntu-24.04_1.snap.stdout
@@ -0,0 +1,5 @@
+- name: alr-repo
+ url: https://gitea.plemya-x.ru/Plemya-x/repo-for-tests
+ ref: main
+ mirrors:
+ - https://github.com/example/example.git
diff --git a/e2e-tests/issue_129_repo_toml_import_test.go b/e2e-tests/issue_129_repo_toml_import_test.go
new file mode 100644
index 0000000..33e0d1d
--- /dev/null
+++ b/e2e-tests/issue_129_repo_toml_import_test.go
@@ -0,0 +1,40 @@
+// ALR - Any Linux Repository
+// Copyright (C) 2025 The ALR Authors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+//go:build e2e
+
+package e2etests_test
+
+import (
+ "testing"
+
+ "go.alt-gnome.ru/capytest"
+)
+
+func TestE2EIssue129RepoTomlImportTest(t *testing.T) {
+ runMatrixSuite(
+ t,
+ "issue-129-repo-toml-import-test",
+ COMMON_SYSTEMS,
+ func(t *testing.T, r capytest.Runner) {
+ defaultPrepare(t, r)
+
+ r.Command("alr", "config", "get", "repos").
+ ExpectStdoutMatchesSnapshot().
+ Run(t)
+ },
+ )
+}
diff --git a/generators/plugin-generator/main.go b/generators/plugin-generator/main.go
new file mode 100644
index 0000000..a488c0e
--- /dev/null
+++ b/generators/plugin-generator/main.go
@@ -0,0 +1,416 @@
+// ALR - Any Linux Repository
+// Copyright (C) 2025 The ALR Authors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/format"
+ "go/parser"
+ "go/token"
+ "log"
+ "os"
+ "strings"
+ "text/template"
+ "unicode"
+
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
+)
+
+type MethodInfo struct {
+ Name string
+ Params []ParamInfo
+ Results []ResultInfo
+ EntityName string
+}
+
+type ParamInfo struct {
+ Name string
+ Type string
+}
+
+type ResultInfo struct {
+ Name string
+ Type string
+ Index int
+}
+
+func extractImports(node *ast.File) []string {
+ var imports []string
+ for _, imp := range node.Imports {
+ if imp.Path.Value != "" {
+ imports = append(imports, imp.Path.Value)
+ }
+ }
+ return imports
+}
+
+func output(path string, buf bytes.Buffer) {
+ formatted, err := format.Source(buf.Bytes())
+ if err != nil {
+ log.Fatalf("formatting: %v", err)
+ }
+
+ outPath := strings.TrimSuffix(path, ".go") + "_gen.go"
+ outFile, err := os.Create(outPath)
+ if err != nil {
+ log.Fatalf("create file: %v", err)
+ }
+ _, err = outFile.Write(formatted)
+ if err != nil {
+ log.Fatalf("writing output: %v", err)
+ }
+ outFile.Close()
+}
+
+func main() {
+ path := os.Getenv("GOFILE")
+ if path == "" {
+ log.Fatal("GOFILE must be set")
+ }
+
+ if len(os.Args) < 2 {
+ log.Fatal("At least one entity name must be provided")
+ }
+
+ entityNames := os.Args[1:]
+
+ fset := token.NewFileSet()
+ node, err := parser.ParseFile(fset, path, nil, parser.AllErrors)
+ if err != nil {
+ log.Fatalf("parsing file: %v", err)
+ }
+
+ packageName := node.Name.Name
+
+ // Find all specified entities
+ entityData := make(map[string][]*ast.Field)
+
+ for _, decl := range node.Decls {
+ genDecl, ok := decl.(*ast.GenDecl)
+ if !ok || genDecl.Tok != token.TYPE {
+ continue
+ }
+ for _, spec := range genDecl.Specs {
+ typeSpec := spec.(*ast.TypeSpec)
+ for _, entityName := range entityNames {
+ if typeSpec.Name.Name == entityName {
+ interfaceType, ok := typeSpec.Type.(*ast.InterfaceType)
+ if !ok {
+ log.Fatalf("entity %s is not an interface", entityName)
+ }
+ entityData[entityName] = interfaceType.Methods.List
+ }
+ }
+ }
+ }
+
+ // Verify all entities were found
+ for _, entityName := range entityNames {
+ if _, found := entityData[entityName]; !found {
+ log.Fatalf("interface %s not found", entityName)
+ }
+ }
+
+ var buf bytes.Buffer
+
+ buf.WriteString(`
+// DO NOT EDIT MANUALLY. This file is generated.
+
+// ALR - Any Linux Repository
+// Copyright (C) 2025 The ALR Authors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+
+`)
+
+ buf.WriteString(fmt.Sprintf("package %s\n", packageName))
+
+ // Generate base structures for all entities
+ baseStructs(&buf, entityNames, extractImports(node))
+
+ // Generate method-specific code for each entity
+ for _, entityName := range entityNames {
+ methods := parseMethodsFromFields(entityName, entityData[entityName])
+ argsGen(&buf, methods)
+ }
+
+ output(path, buf)
+}
+
+func parseMethodsFromFields(entityName string, fields []*ast.Field) []MethodInfo {
+ var methods []MethodInfo
+
+ for _, field := range fields {
+ if len(field.Names) == 0 {
+ continue
+ }
+
+ methodName := field.Names[0].Name
+ funcType, ok := field.Type.(*ast.FuncType)
+ if !ok {
+ continue
+ }
+
+ method := MethodInfo{
+ Name: methodName,
+ EntityName: entityName,
+ }
+
+ // Parse parameters, excluding context.Context
+ if funcType.Params != nil {
+ for i, param := range funcType.Params.List {
+ paramType := typeToString(param.Type)
+ // Skip context.Context parameters
+ if paramType == "context.Context" {
+ continue
+ }
+ if len(param.Names) == 0 {
+ method.Params = append(method.Params, ParamInfo{
+ Name: fmt.Sprintf("Arg%d", i),
+ Type: paramType,
+ })
+ } else {
+ for _, name := range param.Names {
+ method.Params = append(method.Params, ParamInfo{
+ Name: cases.Title(language.Und, cases.NoLower).String(name.Name),
+ Type: paramType,
+ })
+ }
+ }
+ }
+ }
+
+ // Parse results
+ if funcType.Results != nil {
+ resultIndex := 0
+ for _, result := range funcType.Results.List {
+ resultType := typeToString(result.Type)
+ if resultType == "error" {
+ continue // Skip error in response struct
+ }
+
+ if len(result.Names) == 0 {
+ method.Results = append(method.Results, ResultInfo{
+ Name: fmt.Sprintf("Result%d", resultIndex),
+ Type: resultType,
+ Index: resultIndex,
+ })
+ } else {
+ for _, name := range result.Names {
+ method.Results = append(method.Results, ResultInfo{
+ Name: cases.Title(language.Und, cases.NoLower).String(name.Name),
+ Type: resultType,
+ Index: resultIndex,
+ })
+ }
+ }
+ resultIndex++
+ }
+ }
+
+ methods = append(methods, method)
+ }
+
+ return methods
+}
+
+func argsGen(buf *bytes.Buffer, methods []MethodInfo) {
+ // Add template functions first
+ funcMap := template.FuncMap{
+ "lowerFirst": func(s string) string {
+ if len(s) == 0 {
+ return s
+ }
+ return strings.ToLower(s[:1]) + s[1:]
+ },
+ "zeroValue": func(typeName string) string {
+ typeName = strings.TrimSpace(typeName)
+
+ switch typeName {
+ case "string":
+ return "\"\""
+ case "int", "int8", "int16", "int32", "int64":
+ return "0"
+ case "uint", "uint8", "uint16", "uint32", "uint64":
+ return "0"
+ case "float32", "float64":
+ return "0.0"
+ case "bool":
+ return "false"
+ }
+
+ if strings.HasPrefix(typeName, "*") {
+ return "nil"
+ }
+ if strings.HasPrefix(typeName, "[]") ||
+ strings.HasPrefix(typeName, "map[") ||
+ strings.HasPrefix(typeName, "chan ") {
+ return "nil"
+ }
+
+ if typeName == "interface{}" {
+ return "nil"
+ }
+
+ // If external type: pkg.Type
+ if strings.Contains(typeName, ".") {
+ return typeName + "{}"
+ }
+
+ // If starts with uppercase — likely struct
+ if len(typeName) > 0 && unicode.IsUpper(rune(typeName[0])) {
+ return typeName + "{}"
+ }
+
+ return "nil"
+ },
+ }
+
+ argsTemplate := template.Must(template.New("args").Funcs(funcMap).Parse(`
+{{range .}}
+type {{.EntityName}}{{.Name}}Args struct {
+{{range .Params}} {{.Name}} {{.Type}}
+{{end}}}
+
+type {{.EntityName}}{{.Name}}Resp struct {
+{{range .Results}} {{.Name}} {{.Type}}
+{{end}}}
+
+func (s *{{.EntityName}}RPC) {{.Name}}(ctx context.Context, {{range $i, $p := .Params}}{{if $i}}, {{end}}{{lowerFirst $p.Name}} {{$p.Type}}{{end}}) ({{range $i, $r := .Results}}{{if $i}}, {{end}}{{$r.Type}}{{end}}{{if .Results}}, {{end}}error) {
+ var resp *{{.EntityName}}{{.Name}}Resp
+ err := s.client.Call("Plugin.{{.Name}}", &{{.EntityName}}{{.Name}}Args{
+{{range .Params}} {{.Name}}: {{lowerFirst .Name}},
+{{end}} }, &resp)
+ if err != nil {
+ return {{range $i, $r := .Results}}{{if $i}}, {{end}}{{zeroValue $r.Type}}{{end}}{{if .Results}}, {{end}}err
+ }
+ return {{range $i, $r := .Results}}{{if $i}}, {{end}}resp.{{$r.Name}}{{end}}{{if .Results}}, {{end}}nil
+}
+
+func (s *{{.EntityName}}RPCServer) {{.Name}}(args *{{.EntityName}}{{.Name}}Args, resp *{{.EntityName}}{{.Name}}Resp) error {
+ {{if .Results}}{{range $i, $r := .Results}}{{if $i}}, {{end}}{{lowerFirst $r.Name}}{{end}}, err := {{else}}err := {{end}}s.Impl.{{.Name}}(context.Background(),{{range $i, $p := .Params}}{{if $i}}, {{end}}args.{{$p.Name}}{{end}})
+ if err != nil {
+ return err
+ }
+ {{if .Results}}*resp = {{.EntityName}}{{.Name}}Resp{
+{{range .Results}} {{.Name}}: {{lowerFirst .Name}},
+{{end}} }
+ {{else}}*resp = {{.EntityName}}{{.Name}}Resp{}
+ {{end}}return nil
+}
+{{end}}
+`))
+
+ err := argsTemplate.Execute(buf, methods)
+ if err != nil {
+ log.Fatalf("execute args template: %v", err)
+ }
+}
+
+func typeToString(expr ast.Expr) string {
+ switch t := expr.(type) {
+ case *ast.Ident:
+ return t.Name
+ case *ast.StarExpr:
+ return "*" + typeToString(t.X)
+ case *ast.ArrayType:
+ return "[]" + typeToString(t.Elt)
+ case *ast.SelectorExpr:
+ xStr := typeToString(t.X)
+ if xStr == "context" && t.Sel.Name == "Context" {
+ return "context.Context"
+ }
+ return xStr + "." + t.Sel.Name
+ case *ast.InterfaceType:
+ return "interface{}"
+ default:
+ return "interface{}"
+ }
+}
+
+func baseStructs(buf *bytes.Buffer, entityNames, imports []string) {
+ // Ensure "context" is included in imports
+ updatedImports := imports
+ hasContext := false
+ for _, imp := range imports {
+ if strings.Contains(imp, `"context"`) {
+ hasContext = true
+ break
+ }
+ }
+ if !hasContext {
+ updatedImports = append(updatedImports, `"context"`)
+ }
+
+ contentTemplate := template.Must(template.New("").Parse(`
+import (
+ "net/rpc"
+
+ "github.com/hashicorp/go-plugin"
+{{range .Imports}} {{.}}
+{{end}}
+)
+
+{{range .EntityNames}}
+type {{ . }}Plugin struct {
+ Impl {{ . }}
+}
+
+type {{ . }}RPCServer struct {
+ Impl {{ . }}
+}
+
+type {{ . }}RPC struct {
+ client *rpc.Client
+}
+
+func (p *{{ . }}Plugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
+ return &{{ . }}RPC{client: c}, nil
+}
+
+func (p *{{ . }}Plugin) Server(*plugin.MuxBroker) (interface{}, error) {
+ return &{{ . }}RPCServer{Impl: p.Impl}, nil
+}
+
+{{end}}
+`))
+ err := contentTemplate.Execute(buf, struct {
+ EntityNames []string
+ Imports []string
+ }{
+ EntityNames: entityNames,
+ Imports: updatedImports,
+ })
+ if err != nil {
+ log.Fatalf("execute template: %v", err)
+ }
+}
diff --git a/go.mod b/go.mod
index fa734da..b0f26ad 100644
--- a/go.mod
+++ b/go.mod
@@ -34,8 +34,8 @@ require (
github.com/stretchr/testify v1.10.0
github.com/urfave/cli/v2 v2.25.7
github.com/vmihailenco/msgpack/v5 v5.3.5
- go.alt-gnome.ru/capytest v0.0.2
- go.alt-gnome.ru/capytest/providers/podman v0.0.2
+ go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9
+ go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4
golang.org/x/crypto v0.36.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
@@ -77,6 +77,9 @@ require (
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
+ github.com/gkampitakis/ciinfo v0.3.2 // indirect
+ github.com/gkampitakis/go-diff v1.3.2 // indirect
+ github.com/gkampitakis/go-snaps v0.5.13 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
@@ -101,7 +104,10 @@ require (
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
+ github.com/kr/pretty v0.3.1 // indirect
+ github.com/kr/text v0.2.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
+ github.com/maruel/natural v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
@@ -120,6 +126,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
+ github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
@@ -127,6 +134,10 @@ require (
github.com/spf13/cast v1.7.1 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
+ github.com/tidwall/gjson v1.18.0 // indirect
+ github.com/tidwall/match v1.1.1 // indirect
+ github.com/tidwall/pretty v1.2.1 // indirect
+ github.com/tidwall/sjson v1.2.5 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
diff --git a/go.sum b/go.sum
index 64d87f9..d77b9ea 100644
--- a/go.sum
+++ b/go.sum
@@ -104,6 +104,7 @@ github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEyc
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
@@ -134,6 +135,12 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
+github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
+github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
+github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
+github.com/gkampitakis/go-snaps v0.5.13 h1:Hhjmvv1WboSCxkR9iU2mj5PQ8tsz/y8ECGrIbjjPF8Q=
+github.com/gkampitakis/go-snaps v0.5.13/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@@ -286,6 +293,8 @@ github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQ
github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
+github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -345,6 +354,7 @@ github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -358,6 +368,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -393,6 +404,16 @@ github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFd
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
+github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
+github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
+github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
+github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
@@ -413,8 +434,10 @@ gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/Yjui
gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
go.alt-gnome.ru/capytest v0.0.2 h1:clmvIqmYS86hhA1rsvivSSPpfOFkJTpbn38EQP7I3E8=
go.alt-gnome.ru/capytest v0.0.2/go.mod h1:lvxPx3H6h+LPnStBFblgoT2wkjv0wbug3S14troykEg=
-go.alt-gnome.ru/capytest/providers/podman v0.0.2 h1:fTQ9fmYiONgL8dJvyMB+irCfuojIVaomnqto6bl6HjU=
-go.alt-gnome.ru/capytest/providers/podman v0.0.2/go.mod h1:Wpq1Ny3eMzADJpMJArA2TZGZbsviUBmawtEPcxnoerg=
+go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9 h1:NST+V5LV/eLgs0p6PsuvfHiZ4UrIWqftCdifO8zgg0g=
+go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9/go.mod h1:qiM8LARP+JBZr5mrDoVylOoqjrN0MAzvZ21NR9qMc0Y=
+go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9 h1:VZclgdJxARvhZ6PIWWW2hQ6Ge4XeE36pzUr/U/y62bE=
+go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9/go.mod h1:Wpq1Ny3eMzADJpMJArA2TZGZbsviUBmawtEPcxnoerg=
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 h1:Ep54XceQlKhcCHl9awG+wWP4kz4kIP3c3Lzw/Gc/zwY=
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
diff --git a/internal.go b/internal.go
index bbea3db..3080540 100644
--- a/internal.go
+++ b/internal.go
@@ -84,6 +84,43 @@ func InternalBuildCmd() *cli.Command {
}
}
+func InternalReposCmd() *cli.Command {
+ return &cli.Command{
+ Name: "_internal-repos",
+ HideHelp: true,
+ Hidden: true,
+ Action: utils.RootNeededAction(func(ctx *cli.Context) error {
+ logger.SetupForGoPlugin()
+
+ if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
+ return err
+ }
+
+ deps, err := appbuilder.
+ New(ctx.Context).
+ WithConfig().
+ WithDB().
+ WithReposNoPull().
+ Build()
+ if err != nil {
+ return err
+ }
+ defer deps.Defer()
+
+ pluginCfg := build.GetPluginServeCommonConfig()
+ pluginCfg.Plugins = map[string]plugin.Plugin{
+ "repos": &build.ReposExecutorPlugin{
+ Impl: build.NewRepos(
+ deps.Repos,
+ ),
+ },
+ }
+ plugin.Serve(pluginCfg)
+ return nil
+ }),
+ }
+}
+
func InternalInstallCmd() *cli.Command {
return &cli.Command{
Name: "_internal-installer",
@@ -125,7 +162,7 @@ func InternalInstallCmd() *cli.Command {
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: build.HandshakeConfig,
Plugins: map[string]plugin.Plugin{
- "installer": &build.InstallerPlugin{
+ "installer": &build.InstallerExecutorPlugin{
Impl: build.NewInstaller(
manager.Detect(),
),
diff --git a/internal/build/build.go b/internal/build/build.go
index ae3b732..8b33905 100644
--- a/internal/build/build.go
+++ b/internal/build/build.go
@@ -176,25 +176,6 @@ type ScriptResolverExecutor interface {
ResolveScript(ctx context.Context, pkg *alrsh.Package) *ScriptInfo
}
-type ScriptExecutor interface {
- ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error)
- ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error)
- PrepareDirs(
- ctx context.Context,
- input *BuildInput,
- basePkg string,
- ) error
- ExecuteSecondPass(
- ctx context.Context,
- input *BuildInput,
- sf *alrsh.ScriptFile,
- varsOfPackages []*alrsh.Package,
- repoDeps []string,
- builtDeps []*BuiltDep,
- basePkg string,
- ) ([]*BuiltDep, error)
-}
-
type CacheExecutor interface {
CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *alrsh.Package) (string, bool, error)
}
@@ -211,14 +192,6 @@ type CheckerExecutor interface {
) (bool, error)
}
-type InstallerExecutor interface {
- InstallLocal(paths []string, opts *manager.Opts) error
- Install(pkgs []string, opts *manager.Opts) error
- Remove(pkgs []string, opts *manager.Opts) error
-
- RemoveAlreadyInstalled(pkgs []string) ([]string, error)
-}
-
type SourcesInput struct {
Sources []string
Checksums []string
@@ -499,6 +472,7 @@ func (b *Builder) removeBuildDeps(ctx context.Context, input interface {
if remove {
err = b.installerExecutor.Remove(
+ ctx,
deps,
&manager.Opts{
NoConfirm: !input.BuildOpts().Interactive,
@@ -545,6 +519,7 @@ func (b *Builder) InstallALRPackages(
}
err = b.installerExecutor.InstallLocal(
+ ctx,
GetBuiltPaths(res),
&manager.Opts{
NoConfirm: !input.BuildOpts().Interactive,
@@ -645,7 +620,7 @@ func (i *Builder) installBuildDeps(
var deps []string
var err error
if len(pkgs) > 0 {
- deps, err = i.installerExecutor.RemoveAlreadyInstalled(pkgs)
+ deps, err = i.installerExecutor.RemoveAlreadyInstalled(ctx, pkgs)
if err != nil {
return nil, nil, err
}
@@ -668,7 +643,7 @@ func (i *Builder) installOptDeps(
pkgs []string,
) ([]*BuiltDep, error) {
var builtDeps []*BuiltDep
- optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs)
+ optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(ctx, pkgs)
if err != nil {
return nil, err
}
@@ -710,7 +685,7 @@ func (i *Builder) InstallPkgs(
}
if len(builtDeps) > 0 {
- err = i.installerExecutor.InstallLocal(GetBuiltPaths(builtDeps), &manager.Opts{
+ err = i.installerExecutor.InstallLocal(ctx, GetBuiltPaths(builtDeps), &manager.Opts{
NoConfirm: !input.BuildOpts().Interactive,
})
if err != nil {
@@ -719,7 +694,7 @@ func (i *Builder) InstallPkgs(
}
if len(repoDeps) > 0 {
- err = i.installerExecutor.Install(repoDeps, &manager.Opts{
+ err = i.installerExecutor.Install(ctx, repoDeps, &manager.Opts{
NoConfirm: !input.BuildOpts().Interactive,
})
if err != nil {
diff --git a/internal/build/installer.go b/internal/build/installer.go
index eba512e..80973d5 100644
--- a/internal/build/installer.go
+++ b/internal/build/installer.go
@@ -17,6 +17,8 @@
package build
import (
+ "context"
+
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
)
@@ -28,19 +30,19 @@ func NewInstaller(mgr manager.Manager) *Installer {
type Installer struct{ mgr manager.Manager }
-func (i *Installer) InstallLocal(paths []string, opts *manager.Opts) error {
+func (i *Installer) InstallLocal(ctx context.Context, paths []string, opts *manager.Opts) error {
return i.mgr.InstallLocal(opts, paths...)
}
-func (i *Installer) Install(pkgs []string, opts *manager.Opts) error {
+func (i *Installer) Install(ctx context.Context, pkgs []string, opts *manager.Opts) error {
return i.mgr.Install(opts, pkgs...)
}
-func (i *Installer) Remove(pkgs []string, opts *manager.Opts) error {
+func (i *Installer) Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error {
return i.mgr.Remove(opts, pkgs...)
}
-func (i *Installer) RemoveAlreadyInstalled(pkgs []string) ([]string, error) {
+func (i *Installer) RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error) {
filteredPackages := []string{}
for _, dep := range pkgs {
diff --git a/internal/build/plugins.go b/internal/build/plugins.go
new file mode 100644
index 0000000..75e6f3a
--- /dev/null
+++ b/internal/build/plugins.go
@@ -0,0 +1,144 @@
+// ALR - Any Linux Repository
+// Copyright (C) 2025 The ALR Authors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+package build
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "os/exec"
+ "strings"
+ "sync"
+
+ "github.com/hashicorp/go-hclog"
+ "github.com/hashicorp/go-plugin"
+
+ "gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
+)
+
+var pluginMap = map[string]plugin.Plugin{
+ "script-executor": &ScriptExecutorPlugin{},
+ "installer": &InstallerExecutorPlugin{},
+ "repos": &ReposExecutorPlugin{},
+}
+
+var HandshakeConfig = plugin.HandshakeConfig{
+ ProtocolVersion: 1,
+ MagicCookieKey: "ALR_PLUGIN",
+ MagicCookieValue: "-",
+}
+
+func setCommonCmdEnv(cmd *exec.Cmd) {
+ cmd.Env = []string{
+ "HOME=/var/cache/alr",
+ "LOGNAME=alr",
+ "USER=alr",
+ "PATH=/usr/bin:/bin:/usr/local/bin",
+ }
+ for _, env := range os.Environ() {
+ if strings.HasPrefix(env, "LANG=") ||
+ strings.HasPrefix(env, "LANGUAGE=") ||
+ strings.HasPrefix(env, "LC_") ||
+ strings.HasPrefix(env, "ALR_LOG_LEVEL=") {
+ cmd.Env = append(cmd.Env, env)
+ }
+ }
+}
+
+func GetPluginServeCommonConfig() *plugin.ServeConfig {
+ return &plugin.ServeConfig{
+ HandshakeConfig: HandshakeConfig,
+ Logger: hclog.New(&hclog.LoggerOptions{
+ Name: "plugin",
+ Output: os.Stderr,
+ Level: hclog.Trace,
+ JSONFormat: true,
+ DisableTime: true,
+ }),
+ }
+}
+
+func GetSafeInstaller() (InstallerExecutor, func(), error) {
+ return getSafeExecutor[InstallerExecutor]("_internal-installer", "installer")
+}
+
+func GetSafeScriptExecutor() (ScriptExecutor, func(), error) {
+ return getSafeExecutor[ScriptExecutor]("_internal-safe-script-executor", "script-executor")
+}
+
+func GetSafeReposExecutor() (ReposExecutor, func(), error) {
+ return getSafeExecutor[ReposExecutor]("_internal-repos", "repos")
+}
+
+func getSafeExecutor[T any](subCommand, pluginName string) (T, func(), error) {
+ var err error
+
+ executable, err := os.Executable()
+ if err != nil {
+ var zero T
+ return zero, nil, err
+ }
+
+ cmd := exec.Command(executable, subCommand)
+ setCommonCmdEnv(cmd)
+
+ client := plugin.NewClient(&plugin.ClientConfig{
+ HandshakeConfig: HandshakeConfig,
+ Plugins: pluginMap,
+ Cmd: cmd,
+ Logger: logger.GetHCLoggerAdapter(),
+ SkipHostEnv: true,
+ UnixSocketConfig: &plugin.UnixSocketConfig{
+ Group: "alr",
+ },
+ SyncStderr: os.Stderr,
+ })
+ rpcClient, err := client.Client()
+ if err != nil {
+ var zero T
+ return zero, nil, err
+ }
+
+ var cleanupOnce sync.Once
+ cleanup := func() {
+ cleanupOnce.Do(func() {
+ client.Kill()
+ })
+ }
+
+ defer func() {
+ if err != nil {
+ slog.Debug("close executor")
+ cleanup()
+ }
+ }()
+
+ raw, err := rpcClient.Dispense(pluginName)
+ if err != nil {
+ var zero T
+ return zero, nil, err
+ }
+
+ executor, ok := raw.(T)
+ if !ok {
+ var zero T
+ err = fmt.Errorf("dispensed object is not a %T (got %T)", zero, raw)
+ return zero, nil, err
+ }
+
+ return executor, cleanup, nil
+}
diff --git a/internal/build/plugins_executors.go b/internal/build/plugins_executors.go
new file mode 100644
index 0000000..98d388e
--- /dev/null
+++ b/internal/build/plugins_executors.go
@@ -0,0 +1,60 @@
+// ALR - Any Linux Repository
+// Copyright (C) 2025 The ALR Authors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+package build
+
+import (
+ "context"
+
+ "gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
+ "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
+ "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
+)
+
+//go:generate go run ../../generators/plugin-generator InstallerExecutor ScriptExecutor ReposExecutor
+
+// The Executors interfaces must use context.Context as the first parameter,
+// because the plugin-generator cannot generate code without it.
+
+type InstallerExecutor interface {
+ InstallLocal(ctx context.Context, paths []string, opts *manager.Opts) error
+ Install(ctx context.Context, pkgs []string, opts *manager.Opts) error
+ Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error
+ RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error)
+}
+
+type ScriptExecutor interface {
+ ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error)
+ ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error)
+ PrepareDirs(
+ ctx context.Context,
+ input *BuildInput,
+ basePkg string,
+ ) error
+ ExecuteSecondPass(
+ ctx context.Context,
+ input *BuildInput,
+ sf *alrsh.ScriptFile,
+ varsOfPackages []*alrsh.Package,
+ repoDeps []string,
+ builtDeps []*BuiltDep,
+ basePkg string,
+ ) ([]*BuiltDep, error)
+}
+
+type ReposExecutor interface {
+ PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) (types.Repo, error)
+}
diff --git a/internal/build/plugins_executors_gen.go b/internal/build/plugins_executors_gen.go
new file mode 100644
index 0000000..b7e3417
--- /dev/null
+++ b/internal/build/plugins_executors_gen.go
@@ -0,0 +1,369 @@
+// DO NOT EDIT MANUALLY. This file is generated.
+
+// ALR - Any Linux Repository
+// Copyright (C) 2025 The ALR Authors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+package build
+
+import (
+ "net/rpc"
+
+ "context"
+ "gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
+ "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
+ "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
+ "github.com/hashicorp/go-plugin"
+)
+
+type InstallerExecutorPlugin struct {
+ Impl InstallerExecutor
+}
+
+type InstallerExecutorRPCServer struct {
+ Impl InstallerExecutor
+}
+
+type InstallerExecutorRPC struct {
+ client *rpc.Client
+}
+
+func (p *InstallerExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
+ return &InstallerExecutorRPC{client: c}, nil
+}
+
+func (p *InstallerExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
+ return &InstallerExecutorRPCServer{Impl: p.Impl}, nil
+}
+
+type ScriptExecutorPlugin struct {
+ Impl ScriptExecutor
+}
+
+type ScriptExecutorRPCServer struct {
+ Impl ScriptExecutor
+}
+
+type ScriptExecutorRPC struct {
+ client *rpc.Client
+}
+
+func (p *ScriptExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
+ return &ScriptExecutorRPC{client: c}, nil
+}
+
+func (p *ScriptExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
+ return &ScriptExecutorRPCServer{Impl: p.Impl}, nil
+}
+
+type ReposExecutorPlugin struct {
+ Impl ReposExecutor
+}
+
+type ReposExecutorRPCServer struct {
+ Impl ReposExecutor
+}
+
+type ReposExecutorRPC struct {
+ client *rpc.Client
+}
+
+func (p *ReposExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
+ return &ReposExecutorRPC{client: c}, nil
+}
+
+func (p *ReposExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
+ return &ReposExecutorRPCServer{Impl: p.Impl}, nil
+}
+
+type InstallerExecutorInstallLocalArgs struct {
+ Paths []string
+ Opts *manager.Opts
+}
+
+type InstallerExecutorInstallLocalResp struct {
+}
+
+func (s *InstallerExecutorRPC) InstallLocal(ctx context.Context, paths []string, opts *manager.Opts) error {
+ var resp *InstallerExecutorInstallLocalResp
+ err := s.client.Call("Plugin.InstallLocal", &InstallerExecutorInstallLocalArgs{
+ Paths: paths,
+ Opts: opts,
+ }, &resp)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (s *InstallerExecutorRPCServer) InstallLocal(args *InstallerExecutorInstallLocalArgs, resp *InstallerExecutorInstallLocalResp) error {
+ err := s.Impl.InstallLocal(context.Background(), args.Paths, args.Opts)
+ if err != nil {
+ return err
+ }
+ *resp = InstallerExecutorInstallLocalResp{}
+ return nil
+}
+
+type InstallerExecutorInstallArgs struct {
+ Pkgs []string
+ Opts *manager.Opts
+}
+
+type InstallerExecutorInstallResp struct {
+}
+
+func (s *InstallerExecutorRPC) Install(ctx context.Context, pkgs []string, opts *manager.Opts) error {
+ var resp *InstallerExecutorInstallResp
+ err := s.client.Call("Plugin.Install", &InstallerExecutorInstallArgs{
+ Pkgs: pkgs,
+ Opts: opts,
+ }, &resp)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (s *InstallerExecutorRPCServer) Install(args *InstallerExecutorInstallArgs, resp *InstallerExecutorInstallResp) error {
+ err := s.Impl.Install(context.Background(), args.Pkgs, args.Opts)
+ if err != nil {
+ return err
+ }
+ *resp = InstallerExecutorInstallResp{}
+ return nil
+}
+
+type InstallerExecutorRemoveArgs struct {
+ Pkgs []string
+ Opts *manager.Opts
+}
+
+type InstallerExecutorRemoveResp struct {
+}
+
+func (s *InstallerExecutorRPC) Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error {
+ var resp *InstallerExecutorRemoveResp
+ err := s.client.Call("Plugin.Remove", &InstallerExecutorRemoveArgs{
+ Pkgs: pkgs,
+ Opts: opts,
+ }, &resp)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (s *InstallerExecutorRPCServer) Remove(args *InstallerExecutorRemoveArgs, resp *InstallerExecutorRemoveResp) error {
+ err := s.Impl.Remove(context.Background(), args.Pkgs, args.Opts)
+ if err != nil {
+ return err
+ }
+ *resp = InstallerExecutorRemoveResp{}
+ return nil
+}
+
+type InstallerExecutorRemoveAlreadyInstalledArgs struct {
+ Pkgs []string
+}
+
+type InstallerExecutorRemoveAlreadyInstalledResp struct {
+ Result0 []string
+}
+
+func (s *InstallerExecutorRPC) RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error) {
+ var resp *InstallerExecutorRemoveAlreadyInstalledResp
+ err := s.client.Call("Plugin.RemoveAlreadyInstalled", &InstallerExecutorRemoveAlreadyInstalledArgs{
+ Pkgs: pkgs,
+ }, &resp)
+ if err != nil {
+ return nil, err
+ }
+ return resp.Result0, nil
+}
+
+func (s *InstallerExecutorRPCServer) RemoveAlreadyInstalled(args *InstallerExecutorRemoveAlreadyInstalledArgs, resp *InstallerExecutorRemoveAlreadyInstalledResp) error {
+ result0, err := s.Impl.RemoveAlreadyInstalled(context.Background(), args.Pkgs)
+ if err != nil {
+ return err
+ }
+ *resp = InstallerExecutorRemoveAlreadyInstalledResp{
+ Result0: result0,
+ }
+ return nil
+}
+
+type ScriptExecutorReadScriptArgs struct {
+ ScriptPath string
+}
+
+type ScriptExecutorReadScriptResp struct {
+ Result0 *alrsh.ScriptFile
+}
+
+func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) {
+ var resp *ScriptExecutorReadScriptResp
+ err := s.client.Call("Plugin.ReadScript", &ScriptExecutorReadScriptArgs{
+ ScriptPath: scriptPath,
+ }, &resp)
+ if err != nil {
+ return nil, err
+ }
+ return resp.Result0, nil
+}
+
+func (s *ScriptExecutorRPCServer) ReadScript(args *ScriptExecutorReadScriptArgs, resp *ScriptExecutorReadScriptResp) error {
+ result0, err := s.Impl.ReadScript(context.Background(), args.ScriptPath)
+ if err != nil {
+ return err
+ }
+ *resp = ScriptExecutorReadScriptResp{
+ Result0: result0,
+ }
+ return nil
+}
+
+type ScriptExecutorExecuteFirstPassArgs struct {
+ Input *BuildInput
+ Sf *alrsh.ScriptFile
+}
+
+type ScriptExecutorExecuteFirstPassResp struct {
+ Result0 string
+ Result1 []*alrsh.Package
+}
+
+func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) {
+ var resp *ScriptExecutorExecuteFirstPassResp
+ err := s.client.Call("Plugin.ExecuteFirstPass", &ScriptExecutorExecuteFirstPassArgs{
+ Input: input,
+ Sf: sf,
+ }, &resp)
+ if err != nil {
+ return "", nil, err
+ }
+ return resp.Result0, resp.Result1, nil
+}
+
+func (s *ScriptExecutorRPCServer) ExecuteFirstPass(args *ScriptExecutorExecuteFirstPassArgs, resp *ScriptExecutorExecuteFirstPassResp) error {
+ result0, result1, err := s.Impl.ExecuteFirstPass(context.Background(), args.Input, args.Sf)
+ if err != nil {
+ return err
+ }
+ *resp = ScriptExecutorExecuteFirstPassResp{
+ Result0: result0,
+ Result1: result1,
+ }
+ return nil
+}
+
+type ScriptExecutorPrepareDirsArgs struct {
+ Input *BuildInput
+ BasePkg string
+}
+
+type ScriptExecutorPrepareDirsResp struct {
+}
+
+func (s *ScriptExecutorRPC) PrepareDirs(ctx context.Context, input *BuildInput, basePkg string) error {
+ var resp *ScriptExecutorPrepareDirsResp
+ err := s.client.Call("Plugin.PrepareDirs", &ScriptExecutorPrepareDirsArgs{
+ Input: input,
+ BasePkg: basePkg,
+ }, &resp)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (s *ScriptExecutorRPCServer) PrepareDirs(args *ScriptExecutorPrepareDirsArgs, resp *ScriptExecutorPrepareDirsResp) error {
+ err := s.Impl.PrepareDirs(context.Background(), args.Input, args.BasePkg)
+ if err != nil {
+ return err
+ }
+ *resp = ScriptExecutorPrepareDirsResp{}
+ return nil
+}
+
+type ScriptExecutorExecuteSecondPassArgs struct {
+ Input *BuildInput
+ Sf *alrsh.ScriptFile
+ VarsOfPackages []*alrsh.Package
+ RepoDeps []string
+ BuiltDeps []*BuiltDep
+ BasePkg string
+}
+
+type ScriptExecutorExecuteSecondPassResp struct {
+ Result0 []*BuiltDep
+}
+
+func (s *ScriptExecutorRPC) ExecuteSecondPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile, varsOfPackages []*alrsh.Package, repoDeps []string, builtDeps []*BuiltDep, basePkg string) ([]*BuiltDep, error) {
+ var resp *ScriptExecutorExecuteSecondPassResp
+ err := s.client.Call("Plugin.ExecuteSecondPass", &ScriptExecutorExecuteSecondPassArgs{
+ Input: input,
+ Sf: sf,
+ VarsOfPackages: varsOfPackages,
+ RepoDeps: repoDeps,
+ BuiltDeps: builtDeps,
+ BasePkg: basePkg,
+ }, &resp)
+ if err != nil {
+ return nil, err
+ }
+ return resp.Result0, nil
+}
+
+func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ScriptExecutorExecuteSecondPassArgs, resp *ScriptExecutorExecuteSecondPassResp) error {
+ result0, err := s.Impl.ExecuteSecondPass(context.Background(), args.Input, args.Sf, args.VarsOfPackages, args.RepoDeps, args.BuiltDeps, args.BasePkg)
+ if err != nil {
+ return err
+ }
+ *resp = ScriptExecutorExecuteSecondPassResp{
+ Result0: result0,
+ }
+ return nil
+}
+
+type ReposExecutorPullOneAndUpdateFromConfigArgs struct {
+ Repo *types.Repo
+}
+
+type ReposExecutorPullOneAndUpdateFromConfigResp struct {
+ Result0 types.Repo
+}
+
+func (s *ReposExecutorRPC) PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) (types.Repo, error) {
+ var resp *ReposExecutorPullOneAndUpdateFromConfigResp
+ err := s.client.Call("Plugin.PullOneAndUpdateFromConfig", &ReposExecutorPullOneAndUpdateFromConfigArgs{
+ Repo: repo,
+ }, &resp)
+ if err != nil {
+ return types.Repo{}, err
+ }
+ return resp.Result0, nil
+}
+
+func (s *ReposExecutorRPCServer) PullOneAndUpdateFromConfig(args *ReposExecutorPullOneAndUpdateFromConfigArgs, resp *ReposExecutorPullOneAndUpdateFromConfigResp) error {
+ result0, err := s.Impl.PullOneAndUpdateFromConfig(context.Background(), args.Repo)
+ if err != nil {
+ return err
+ }
+ *resp = ReposExecutorPullOneAndUpdateFromConfigResp{
+ Result0: result0,
+ }
+ return nil
+}
diff --git a/internal/build/safe_common.go b/internal/build/repos_executor.go
similarity index 62%
rename from internal/build/safe_common.go
rename to internal/build/repos_executor.go
index 33cb66f..4336a0b 100644
--- a/internal/build/safe_common.go
+++ b/internal/build/repos_executor.go
@@ -17,24 +17,21 @@
package build
import (
- "os"
- "os/exec"
- "strings"
+ "context"
+
+ "gitea.plemya-x.ru/Plemya-x/ALR/internal/repos"
+ "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
-func setCommonCmdEnv(cmd *exec.Cmd) {
- cmd.Env = []string{
- "HOME=/var/cache/alr",
- "LOGNAME=alr",
- "USER=alr",
- "PATH=/usr/bin:/bin:/usr/local/bin",
- }
- for _, env := range os.Environ() {
- if strings.HasPrefix(env, "LANG=") ||
- strings.HasPrefix(env, "LANGUAGE=") ||
- strings.HasPrefix(env, "LC_") ||
- strings.HasPrefix(env, "ALR_LOG_LEVEL=") {
- cmd.Env = append(cmd.Env, env)
- }
- }
+type reposExecutor struct{ r *repos.Repos }
+
+func NewRepos(r *repos.Repos) ReposExecutor {
+ return &reposExecutor{r}
+}
+
+func (r *reposExecutor) PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) (types.Repo, error) {
+ if err := r.r.PullOneAndUpdateFromConfig(ctx, repo); err != nil {
+ return *repo, err
+ }
+ return *repo, nil
}
diff --git a/internal/build/safe_installer.go b/internal/build/safe_installer.go
deleted file mode 100644
index e7c8447..0000000
--- a/internal/build/safe_installer.go
+++ /dev/null
@@ -1,161 +0,0 @@
-// ALR - Any Linux Repository
-// Copyright (C) 2025 The ALR Authors
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see .
-
-package build
-
-import (
- "fmt"
- "log/slog"
- "net/rpc"
- "os"
- "os/exec"
- "sync"
- "syscall"
-
- "github.com/hashicorp/go-plugin"
-
- "gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
- "gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
-)
-
-type InstallerPlugin struct {
- Impl InstallerExecutor
-}
-
-type InstallerRPC struct {
- client *rpc.Client
-}
-
-type InstallerRPCServer struct {
- Impl InstallerExecutor
-}
-
-type InstallArgs struct {
- PackagesOrPaths []string
- Opts *manager.Opts
-}
-
-func (r *InstallerRPC) InstallLocal(paths []string, opts *manager.Opts) error {
- return r.client.Call("Plugin.InstallLocal", &InstallArgs{
- PackagesOrPaths: paths,
- Opts: opts,
- }, nil)
-}
-
-func (s *InstallerRPCServer) InstallLocal(args *InstallArgs, reply *struct{}) error {
- return s.Impl.InstallLocal(args.PackagesOrPaths, args.Opts)
-}
-
-func (r *InstallerRPC) Install(pkgs []string, opts *manager.Opts) error {
- return r.client.Call("Plugin.Install", &InstallArgs{
- PackagesOrPaths: pkgs,
- Opts: opts,
- }, nil)
-}
-
-func (s *InstallerRPCServer) Install(args *InstallArgs, reply *struct{}) error {
- return s.Impl.Install(args.PackagesOrPaths, args.Opts)
-}
-
-func (r *InstallerRPC) Remove(pkgs []string, opts *manager.Opts) error {
- return r.client.Call("Plugin.Remove", &InstallArgs{
- PackagesOrPaths: pkgs,
- Opts: opts,
- }, nil)
-}
-
-func (s *InstallerRPCServer) Remove(args *InstallArgs, reply *struct{}) error {
- return s.Impl.Remove(args.PackagesOrPaths, args.Opts)
-}
-
-func (r *InstallerRPC) RemoveAlreadyInstalled(paths []string) ([]string, error) {
- var val []string
- err := r.client.Call("Plugin.RemoveAlreadyInstalled", paths, &val)
- return val, err
-}
-
-func (s *InstallerRPCServer) RemoveAlreadyInstalled(pkgs []string, res *[]string) error {
- vars, err := s.Impl.RemoveAlreadyInstalled(pkgs)
- if err != nil {
- return err
- }
- *res = vars
- return nil
-}
-
-func (p *InstallerPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
- return &InstallerRPC{client: c}, nil
-}
-
-func (p *InstallerPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
- return &InstallerRPCServer{Impl: p.Impl}, nil
-}
-
-func GetSafeInstaller() (InstallerExecutor, func(), error) {
- var err error
-
- executable, err := os.Executable()
- if err != nil {
- return nil, nil, err
- }
- cmd := exec.Command(executable, "_internal-installer")
- setCommonCmdEnv(cmd)
-
- slog.Debug("safe installer setup", "uid", syscall.Getuid(), "gid", syscall.Getgid())
-
- client := plugin.NewClient(&plugin.ClientConfig{
- HandshakeConfig: HandshakeConfig,
- Plugins: pluginMap,
- Cmd: cmd,
- Logger: logger.GetHCLoggerAdapter(),
- SkipHostEnv: true,
- UnixSocketConfig: &plugin.UnixSocketConfig{
- Group: "alr",
- },
- SyncStderr: os.Stderr,
- })
- rpcClient, err := client.Client()
- if err != nil {
- return nil, nil, err
- }
-
- var cleanupOnce sync.Once
- cleanup := func() {
- cleanupOnce.Do(func() {
- client.Kill()
- })
- }
-
- defer func() {
- if err != nil {
- slog.Debug("close installer")
- cleanup()
- }
- }()
-
- raw, err := rpcClient.Dispense("installer")
- if err != nil {
- return nil, nil, err
- }
-
- executor, ok := raw.(InstallerExecutor)
- if !ok {
- err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw)
- return nil, nil, err
- }
-
- return executor, cleanup, nil
-}
diff --git a/internal/build/safe_script_executor.go b/internal/build/safe_script_executor.go
deleted file mode 100644
index 3342e7b..0000000
--- a/internal/build/safe_script_executor.go
+++ /dev/null
@@ -1,273 +0,0 @@
-// ALR - Any Linux Repository
-// Copyright (C) 2025 The ALR Authors
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see .
-
-package build
-
-import (
- "context"
- "fmt"
- "log/slog"
- "net/rpc"
- "os"
- "os/exec"
- "sync"
-
- "github.com/hashicorp/go-plugin"
-
- "gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
- "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
-)
-
-var HandshakeConfig = plugin.HandshakeConfig{
- ProtocolVersion: 1,
- MagicCookieKey: "ALR_PLUGIN",
- MagicCookieValue: "-",
-}
-
-type ScriptExecutorPlugin struct {
- Impl ScriptExecutor
-}
-
-type ScriptExecutorRPCServer struct {
- Impl ScriptExecutor
-}
-
-// =============================
-//
-// ReadScript
-//
-
-func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) {
- var resp *alrsh.ScriptFile
- err := s.client.Call("Plugin.ReadScript", scriptPath, &resp)
- return resp, err
-}
-
-func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *alrsh.ScriptFile) error {
- file, err := s.Impl.ReadScript(context.Background(), scriptPath)
- if err != nil {
- return err
- }
- *resp = *file
- return nil
-}
-
-// =============================
-//
-// ExecuteFirstPass
-//
-
-type ExecuteFirstPassArgs struct {
- Input *BuildInput
- Sf *alrsh.ScriptFile
-}
-
-type ExecuteFirstPassResp struct {
- BasePkg string
- VarsOfPackages []*alrsh.Package
-}
-
-func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) {
- var resp *ExecuteFirstPassResp
- err := s.client.Call("Plugin.ExecuteFirstPass", &ExecuteFirstPassArgs{
- Input: input,
- Sf: sf,
- }, &resp)
- if err != nil {
- return "", nil, err
- }
- return resp.BasePkg, resp.VarsOfPackages, nil
-}
-
-func (s *ScriptExecutorRPCServer) ExecuteFirstPass(args *ExecuteFirstPassArgs, resp *ExecuteFirstPassResp) error {
- basePkg, varsOfPackages, err := s.Impl.ExecuteFirstPass(context.Background(), args.Input, args.Sf)
- if err != nil {
- return err
- }
- *resp = ExecuteFirstPassResp{
- BasePkg: basePkg,
- VarsOfPackages: varsOfPackages,
- }
- return nil
-}
-
-// =============================
-//
-// PrepareDirs
-//
-
-type PrepareDirsArgs struct {
- Input *BuildInput
- BasePkg string
-}
-
-func (s *ScriptExecutorRPC) PrepareDirs(
- ctx context.Context,
- input *BuildInput,
- basePkg string,
-) error {
- err := s.client.Call("Plugin.PrepareDirs", &PrepareDirsArgs{
- Input: input,
- BasePkg: basePkg,
- }, nil)
- if err != nil {
- return err
- }
- return err
-}
-
-func (s *ScriptExecutorRPCServer) PrepareDirs(args *PrepareDirsArgs, reply *struct{}) error {
- err := s.Impl.PrepareDirs(
- context.Background(),
- args.Input,
- args.BasePkg,
- )
- if err != nil {
- return err
- }
- return err
-}
-
-// =============================
-//
-// ExecuteSecondPass
-//
-
-type ExecuteSecondPassArgs struct {
- Input *BuildInput
- Sf *alrsh.ScriptFile
- VarsOfPackages []*alrsh.Package
- RepoDeps []string
- BuiltDeps []*BuiltDep
- BasePkg string
-}
-
-func (s *ScriptExecutorRPC) ExecuteSecondPass(
- ctx context.Context,
- input *BuildInput,
- sf *alrsh.ScriptFile,
- varsOfPackages []*alrsh.Package,
- repoDeps []string,
- builtDeps []*BuiltDep,
- basePkg string,
-) ([]*BuiltDep, error) {
- var resp []*BuiltDep
- err := s.client.Call("Plugin.ExecuteSecondPass", &ExecuteSecondPassArgs{
- Input: input,
- Sf: sf,
- VarsOfPackages: varsOfPackages,
- RepoDeps: repoDeps,
- BuiltDeps: builtDeps,
- BasePkg: basePkg,
- }, &resp)
- if err != nil {
- return nil, err
- }
- return resp, nil
-}
-
-func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ExecuteSecondPassArgs, resp *[]*BuiltDep) error {
- res, err := s.Impl.ExecuteSecondPass(
- context.Background(),
- args.Input,
- args.Sf,
- args.VarsOfPackages,
- args.RepoDeps,
- args.BuiltDeps,
- args.BasePkg,
- )
- if err != nil {
- return err
- }
- *resp = res
- return err
-}
-
-//
-// ============================
-//
-
-func (p *ScriptExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
- return &ScriptExecutorRPCServer{Impl: p.Impl}, nil
-}
-
-func (p *ScriptExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
- return &ScriptExecutorRPC{client: c}, nil
-}
-
-type ScriptExecutorRPC struct {
- client *rpc.Client
-}
-
-var pluginMap = map[string]plugin.Plugin{
- "script-executor": &ScriptExecutorPlugin{},
- "installer": &InstallerPlugin{},
-}
-
-func GetSafeScriptExecutor() (ScriptExecutor, func(), error) {
- var err error
-
- executable, err := os.Executable()
- if err != nil {
- return nil, nil, err
- }
-
- cmd := exec.Command(executable, "_internal-safe-script-executor")
- setCommonCmdEnv(cmd)
-
- client := plugin.NewClient(&plugin.ClientConfig{
- HandshakeConfig: HandshakeConfig,
- Plugins: pluginMap,
- Cmd: cmd,
- Logger: logger.GetHCLoggerAdapter(),
- SkipHostEnv: true,
- UnixSocketConfig: &plugin.UnixSocketConfig{
- Group: "alr",
- },
- SyncStderr: os.Stderr,
- })
- rpcClient, err := client.Client()
- if err != nil {
- return nil, nil, err
- }
-
- var cleanupOnce sync.Once
- cleanup := func() {
- cleanupOnce.Do(func() {
- client.Kill()
- })
- }
-
- defer func() {
- if err != nil {
- slog.Debug("close script-executor")
- cleanup()
- }
- }()
-
- raw, err := rpcClient.Dispense("script-executor")
- if err != nil {
- return nil, nil, err
- }
-
- executor, ok := raw.(ScriptExecutor)
- if !ok {
- err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw)
- return nil, nil, err
- }
-
- return executor, cleanup, nil
-}
diff --git a/internal/repos/pull.go b/internal/repos/pull.go
index e0944d8..f0da904 100644
--- a/internal/repos/pull.go
+++ b/internal/repos/pull.go
@@ -68,7 +68,7 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
}
for _, repo := range repos {
- err := rs.pullRepo(ctx, repo)
+ err := rs.pullRepo(ctx, &repo, false)
if err != nil {
return err
}
@@ -77,7 +77,16 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
return nil
}
-func (rs *Repos) pullRepo(ctx context.Context, repo types.Repo) error {
+func (rs *Repos) PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) error {
+ err := rs.pullRepo(ctx, repo, true)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (rs *Repos) pullRepo(ctx context.Context, repo *types.Repo, updateRepoFromToml bool) error {
urls := []string{repo.URL}
urls = append(urls, repo.Mirrors...)
@@ -88,7 +97,7 @@ func (rs *Repos) pullRepo(ctx context.Context, repo types.Repo) error {
slog.Info(gotext.Get("Trying mirror"), "repo", repo.Name, "mirror", repoURL)
}
- err := rs.pullRepoFromURL(ctx, repoURL, repo)
+ err := rs.pullRepoFromURL(ctx, repoURL, repo, updateRepoFromToml)
if err != nil {
lastErr = err
slog.Warn(gotext.Get("Failed to pull from URL"), "repo", repo.Name, "url", repoURL, "error", err)
@@ -149,7 +158,7 @@ func readGitRepo(repoDir, repoUrl string) (*git.Repository, bool, error) {
return r, true, nil
}
-func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo types.Repo) error {
+func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo *types.Repo, update bool) error {
repoURL, err := url.Parse(rawRepoUrl)
if err != nil {
return fmt.Errorf("invalid URL %s: %w", rawRepoUrl, err)
@@ -214,12 +223,12 @@ func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo ty
// empty. In this case, we need to update the DB fully
// rather than just incrementally.
if rs.db.IsEmpty() || freshGit {
- err = rs.processRepoFull(ctx, repo, repoDir)
+ err = rs.processRepoFull(ctx, *repo, repoDir)
if err != nil {
return err
}
} else {
- err = rs.processRepoChanges(ctx, repo, r, w, old, new)
+ err = rs.processRepoChanges(ctx, *repo, r, w, old, new)
if err != nil {
return err
}
@@ -247,6 +256,18 @@ func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo ty
}
}
+ if update {
+ if repoCfg.Repo.URL != "" {
+ repo.URL = repoCfg.Repo.URL
+ }
+ if repoCfg.Repo.Ref != "" {
+ repo.Ref = repoCfg.Repo.Ref
+ }
+ if len(repoCfg.Repo.Mirrors) > 0 {
+ repo.Mirrors = repoCfg.Repo.Mirrors
+ }
+ }
+
return nil
}
diff --git a/internal/translations/default.pot b/internal/translations/default.pot
index 354adf8..687cd59 100644
--- a/internal/translations/default.pot
+++ b/internal/translations/default.pot
@@ -218,23 +218,23 @@ msgstr ""
msgid "Error removing packages"
msgstr ""
-#: internal/build/build.go:378
+#: internal/build/build.go:351
msgid "Building package"
msgstr ""
-#: internal/build/build.go:407
+#: internal/build/build.go:380
msgid "The checksums array must be the same length as sources"
msgstr ""
-#: internal/build/build.go:449
+#: internal/build/build.go:422
msgid "Downloading sources"
msgstr ""
-#: internal/build/build.go:495
+#: internal/build/build.go:468
msgid "Would you like to remove the build dependencies?"
msgstr ""
-#: internal/build/build.go:571
+#: internal/build/build.go:546
msgid "Installing dependencies"
msgstr ""
@@ -407,27 +407,27 @@ msgstr ""
msgid "ERROR"
msgstr ""
-#: internal/repos/pull.go:88
+#: internal/repos/pull.go:97
msgid "Trying mirror"
msgstr ""
-#: internal/repos/pull.go:94
+#: internal/repos/pull.go:103
msgid "Failed to pull from URL"
msgstr ""
-#: internal/repos/pull.go:158
+#: internal/repos/pull.go:167
msgid "Pulling repository"
msgstr ""
-#: internal/repos/pull.go:195
+#: internal/repos/pull.go:204
msgid "Repository up to date"
msgstr ""
-#: internal/repos/pull.go:230
+#: internal/repos/pull.go:239
msgid "Git repository does not appear to be a valid ALR repo"
msgstr ""
-#: internal/repos/pull.go:246
+#: internal/repos/pull.go:255
msgid ""
"ALR repo's minimum ALR version is greater than the current version. Try "
"updating ALR if something doesn't work."
@@ -481,11 +481,11 @@ msgstr ""
msgid "Enable interactive questions and prompts"
msgstr ""
-#: main.go:147
+#: main.go:148
msgid "Show help"
msgstr ""
-#: main.go:151
+#: main.go:152
msgid "Error while running app"
msgstr ""
@@ -517,44 +517,44 @@ msgstr ""
msgid "Pull all repositories that have changed"
msgstr ""
-#: repo.go:41
+#: repo.go:42
msgid "Manage repos"
msgstr ""
-#: repo.go:55 repo.go:625
+#: repo.go:56 repo.go:625
msgid "Remove an existing repository"
msgstr ""
-#: repo.go:57 repo.go:521
+#: repo.go:58 repo.go:521
msgid ""
msgstr ""
-#: repo.go:102 repo.go:465 repo.go:568
+#: repo.go:103 repo.go:465 repo.go:568
msgid "Repo \"%s\" does not exist"
msgstr ""
-#: repo.go:109
+#: repo.go:110
msgid "Error removing repo directory"
msgstr ""
-#: repo.go:113 repo.go:180 repo.go:253 repo.go:316 repo.go:389 repo.go:504
+#: repo.go:114 repo.go:195 repo.go:253 repo.go:316 repo.go:389 repo.go:504
#: repo.go:576
msgid "Error saving config"
msgstr ""
-#: repo.go:132
+#: repo.go:133
msgid "Error removing packages from database"
msgstr ""
-#: repo.go:143 repo.go:595
+#: repo.go:144 repo.go:595
msgid "Add a new repository"
msgstr ""
-#: repo.go:144 repo.go:270 repo.go:345 repo.go:402
+#: repo.go:145 repo.go:270 repo.go:345 repo.go:402
msgid " "
msgstr ""
-#: repo.go:169
+#: repo.go:170
msgid "Repo \"%s\" already exists"
msgstr ""
diff --git a/internal/translations/po/ru/default.po b/internal/translations/po/ru/default.po
index f2c9932..6d9ba8f 100644
--- a/internal/translations/po/ru/default.po
+++ b/internal/translations/po/ru/default.po
@@ -225,23 +225,23 @@ msgstr "Для команды remove ожидался хотя бы 1 аргум
msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов"
-#: internal/build/build.go:378
+#: internal/build/build.go:351
msgid "Building package"
msgstr "Сборка пакета"
-#: internal/build/build.go:407
+#: internal/build/build.go:380
msgid "The checksums array must be the same length as sources"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
-#: internal/build/build.go:449
+#: internal/build/build.go:422
msgid "Downloading sources"
msgstr "Скачивание источников"
-#: internal/build/build.go:495
+#: internal/build/build.go:468
msgid "Would you like to remove the build dependencies?"
msgstr "Хотели бы вы удалить зависимости сборки?"
-#: internal/build/build.go:571
+#: internal/build/build.go:546
msgid "Installing dependencies"
msgstr "Установка зависимостей"
@@ -421,27 +421,27 @@ msgstr ""
msgid "ERROR"
msgstr "ОШИБКА"
-#: internal/repos/pull.go:88
+#: internal/repos/pull.go:97
msgid "Trying mirror"
msgstr "Пробую зеркало"
-#: internal/repos/pull.go:94
+#: internal/repos/pull.go:103
msgid "Failed to pull from URL"
msgstr "Не удалось извлечь из URL"
-#: internal/repos/pull.go:158
+#: internal/repos/pull.go:167
msgid "Pulling repository"
msgstr "Скачивание репозитория"
-#: internal/repos/pull.go:195
+#: internal/repos/pull.go:204
msgid "Repository up to date"
msgstr "Репозиторий уже обновлён"
-#: internal/repos/pull.go:230
+#: internal/repos/pull.go:239
msgid "Git repository does not appear to be a valid ALR repo"
msgstr "Репозиторий Git не поддерживается репозиторием ALR"
-#: internal/repos/pull.go:246
+#: internal/repos/pull.go:255
msgid ""
"ALR repo's minimum ALR version is greater than the current version. Try "
"updating ALR if something doesn't work."
@@ -497,11 +497,11 @@ msgstr "Аргументы, которые будут переданы мене
msgid "Enable interactive questions and prompts"
msgstr "Включение интерактивных вопросов и запросов"
-#: main.go:147
+#: main.go:148
msgid "Show help"
msgstr "Показать справку"
-#: main.go:151
+#: main.go:152
msgid "Error while running app"
msgstr "Ошибка при запуске приложения"
@@ -533,44 +533,44 @@ msgstr "%s %s загружается — %s/с\n"
msgid "Pull all repositories that have changed"
msgstr "Скачать все изменённые репозитории"
-#: repo.go:41
+#: repo.go:42
msgid "Manage repos"
msgstr "Управление репозиториями"
-#: repo.go:55 repo.go:625
+#: repo.go:56 repo.go:625
msgid "Remove an existing repository"
msgstr "Удалить существующий репозиторий"
-#: repo.go:57 repo.go:521
+#: repo.go:58 repo.go:521
msgid ""
msgstr "<имя>"
-#: repo.go:102 repo.go:465 repo.go:568
+#: repo.go:103 repo.go:465 repo.go:568
msgid "Repo \"%s\" does not exist"
msgstr "Репозитория \"%s\" не существует"
-#: repo.go:109
+#: repo.go:110
msgid "Error removing repo directory"
msgstr "Ошибка при удалении каталога репозитория"
-#: repo.go:113 repo.go:180 repo.go:253 repo.go:316 repo.go:389 repo.go:504
+#: repo.go:114 repo.go:195 repo.go:253 repo.go:316 repo.go:389 repo.go:504
#: repo.go:576
msgid "Error saving config"
msgstr "Ошибка при сохранении конфигурации"
-#: repo.go:132
+#: repo.go:133
msgid "Error removing packages from database"
msgstr "Ошибка при удалении пакетов из базы данных"
-#: repo.go:143 repo.go:595
+#: repo.go:144 repo.go:595
msgid "Add a new repository"
msgstr "Добавить новый репозиторий"
-#: repo.go:144 repo.go:270 repo.go:345 repo.go:402
+#: repo.go:145 repo.go:270 repo.go:345 repo.go:402
msgid " "
msgstr "<имя> "
-#: repo.go:169
+#: repo.go:170
msgid "Repo \"%s\" already exists"
msgstr "Репозиторий \"%s\" уже существует"
diff --git a/internal/utils/cmd.go b/internal/utils/cmd.go
index 5753f2f..3b4025f 100644
--- a/internal/utils/cmd.go
+++ b/internal/utils/cmd.go
@@ -131,11 +131,11 @@ func EnsureIsAlrUser() error {
}
newUid := syscall.Getuid()
if newUid != uid {
- return errors.New("new uid don't matches requested")
+ return errors.New("uid don't matches requested")
}
newGid := syscall.Getgid()
if newGid != gid {
- return errors.New("new gid don't matches requested")
+ return errors.New("gid don't matches requested")
}
return nil
}
diff --git a/main.go b/main.go
index af84885..368ec3d 100644
--- a/main.go
+++ b/main.go
@@ -88,6 +88,7 @@ func GetApp() *cli.App {
InternalBuildCmd(),
InternalInstallCmd(),
InternalMountCmd(),
+ InternalReposCmd(),
},
Before: func(c *cli.Context) error {
if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" {
diff --git a/pkg/dl/git.go b/pkg/dl/git.go
index b5380b1..26f8de5 100644
--- a/pkg/dl/git.go
+++ b/pkg/dl/git.go
@@ -22,6 +22,7 @@ package dl
import (
"context"
"errors"
+ "fmt"
"net/url"
"path"
"strconv"
@@ -149,6 +150,7 @@ func (d *GitDownloader) Update(opts Options) (bool, error) {
u.Scheme = strings.TrimPrefix(u.Scheme, "git+")
query := u.Query()
+ rev := query.Get("~rev")
query.Del("~rev")
depthStr := query.Get("~depth")
@@ -177,27 +179,67 @@ func (d *GitDownloader) Update(opts Options) (bool, error) {
}
}
- po := &git.PullOptions{
- Depth: depth,
- Progress: opts.Progress,
- RecurseSubmodules: git.NoRecurseSubmodules,
- }
-
- if recursive == "true" {
- po.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth
+ // First, we do a fetch to get all the revisions.
+ fo := &git.FetchOptions{
+ Depth: depth,
+ Progress: opts.Progress,
}
m, err := getManifest(opts.Destination)
manifestOK := err == nil
- err = w.Pull(po)
- if err != nil {
- if errors.Is(err, git.NoErrAlreadyUpToDate) {
- return false, nil
- }
+ err = r.Fetch(fo)
+ if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
return false, err
}
+ // If a revision is specified, switch to it.
+ if rev != "" {
+ // We are trying to find the revision as a hash of the commit
+ hash, err := r.ResolveRevision(plumbing.Revision(rev))
+ if err != nil {
+ return false, fmt.Errorf("failed to resolve revision %s: %w", rev, err)
+ }
+
+ err = w.Checkout(&git.CheckoutOptions{
+ Hash: *hash,
+ })
+ if err != nil {
+ return false, fmt.Errorf("failed to checkout revision %s: %w", rev, err)
+ }
+
+ if recursive == "true" {
+ submodules, err := w.Submodules()
+ if err == nil {
+ err = submodules.Update(&git.SubmoduleUpdateOptions{
+ Init: true,
+ })
+ if err != nil {
+ return false, fmt.Errorf("failed to update submodules %s: %w", rev, err)
+ }
+ }
+ }
+ } else {
+ // If the revision is not specified, we do a regular pull.
+ po := &git.PullOptions{
+ Depth: depth,
+ Progress: opts.Progress,
+ RecurseSubmodules: git.NoRecurseSubmodules,
+ }
+
+ if recursive == "true" {
+ po.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth
+ }
+
+ err = w.Pull(po)
+ if err != nil {
+ if errors.Is(err, git.NoErrAlreadyUpToDate) {
+ return false, nil
+ }
+ return false, err
+ }
+ }
+
err = VerifyHashFromLocal("", opts)
if err != nil {
return false, err
diff --git a/pkg/dl/git_test.go b/pkg/dl/git_test.go
index 1c65db4..eaf3fbf 100644
--- a/pkg/dl/git_test.go
+++ b/pkg/dl/git_test.go
@@ -153,7 +153,7 @@ func TestGitDownloaderUpdate(t *testing.T) {
assert.NoError(t, err)
updated, err := d.Update(dl.Options{
- URL: "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git~rev=test-update-git-downloader",
+ URL: "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git?~rev=test-update-git-downloader",
Destination: dest,
Hash: hsh,
HashAlgorithm: "sha256",
diff --git a/pkg/types/repo.go b/pkg/types/repo.go
index 43ef92d..4c6ad48 100644
--- a/pkg/types/repo.go
+++ b/pkg/types/repo.go
@@ -22,6 +22,9 @@ package types
// RepoConfig represents a ALR repo's alr-repo.toml file.
type RepoConfig struct {
Repo struct {
- MinVersion string `toml:"minVersion"`
+ MinVersion string `toml:"minVersion"`
+ URL string `toml:"url"`
+ Ref string `toml:"ref"`
+ Mirrors []string `toml:"mirrors"`
}
}
diff --git a/repo.go b/repo.go
index 7fc9e4b..5669e5f 100644
--- a/repo.go
+++ b/repo.go
@@ -29,6 +29,7 @@ import (
"github.com/urfave/cli/v2"
"golang.org/x/exp/slices"
+ "gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
@@ -169,10 +170,24 @@ func AddRepoCmd() *cli.Command {
return cliutils.FormatCliExit(gotext.Get("Repo \"%s\" already exists", repo.Name), nil)
}
}
- reposSlice = append(reposSlice, types.Repo{
+
+ newRepo := types.Repo{
Name: name,
URL: repoURL,
- })
+ }
+
+ r, close, err := build.GetSafeReposExecutor()
+ if err != nil {
+ return err
+ }
+ defer close()
+
+ newRepo, err = r.PullOneAndUpdateFromConfig(c.Context, &newRepo)
+ if err != nil {
+ return err
+ }
+
+ reposSlice = append(reposSlice, newRepo)
cfg.SetRepos(reposSlice)
err = cfg.System.Save()
@@ -180,21 +195,6 @@ func AddRepoCmd() *cli.Command {
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
}
- if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
- return err
- }
-
- deps, err = appbuilder.
- New(ctx).
- UseConfig(cfg).
- WithDB().
- WithReposForcePull().
- Build()
- if err != nil {
- return err
- }
- defer deps.Defer()
-
return nil
}),
}