Compare commits

..

No commits in common. "57225e05bd7e9d33679ec5643b7429a6f6fafb08" and "587abf7aad69836e31633831d6d7748d78cb83d9" have entirely different histories.

31 changed files with 909 additions and 1159 deletions

@ -11,7 +11,7 @@
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="33.5" y="14">coverage</text> <text x="33.5" y="14">coverage</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">15.8%</text> <text x="86" y="15" fill="#010101" fill-opacity=".3">15.7%</text>
<text x="86" y="14">15.8%</text> <text x="86" y="14">15.7%</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 926 B

@ -12,7 +12,7 @@
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="37" y="15" fill="#010101" fill-opacity=".3">ru translate</text> <text x="37" y="15" fill="#010101" fill-opacity=".3">ru translate</text>
<text x="37" y="14">ru translate</text> <text x="37" y="14">ru translate</text>
<text x="100" y="15" fill="#010101" fill-opacity=".3">97.00%</text> <text x="100" y="15" fill="#010101" fill-opacity=".3">98.00%</text>
<text x="100" y="14">97.00%</text> <text x="100" y="14">98.00%</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 940 B

After

Width:  |  Height:  |  Size: 940 B

232
build.go

@ -20,20 +20,25 @@
package main package main
import ( import (
"bytes"
"log/slog" "log/slog"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
func BuildCmd() *cli.Command { func BuildCmd() *cli.Command {
@ -64,66 +69,115 @@ func BuildCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.EnuseIsPrivilegedGroupMember(); err != nil {
return nil
}
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err) slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
}
executable, err := os.Executable()
if err != nil {
slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
} }
wd, wdCleanup, err := Mount(wd) cmd := exec.Command(executable, "_internal-mount", wd)
var stdout bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
}
wd = stdout.String()
defer func() {
slog.Warn("unmounting...")
cmd := exec.Command(executable, "_internal-umount", wd)
var stdout bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
}
}()
err = utils.DropCapsToAlrUser()
if err != nil {
slog.Error(gotext.Get("Error dropping capabilities"), "err", err)
os.Exit(1)
}
_, err = os.Stat(wd)
if err != nil {
slog.Error(gotext.Get("Error dropping capabilities"), "err", err)
os.Exit(1)
} }
defer wdCleanup()
ctx := c.Context ctx := c.Context
cfg := config.New()
deps, err := appbuilder. err = cfg.Load()
New(ctx).
WithConfig().
WithDB().
WithReposNoPull().
WithDistroInfo().
WithManager().
Build()
if err != nil { if err != nil {
return cli.Exit(err, 1) slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg)
rs := repos.New(cfg, db)
err = db.Init(ctx)
if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
} }
defer deps.Defer()
var script string var script string
var packages []string var packages []string
// Обнаружение менеджера пакетов
mgr := manager.Detect()
if mgr == nil {
slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
}
info, err := distro.ParseOSRelease(ctx)
if err != nil {
slog.Error(gotext.Get("Error parsing os release"), "err", err)
os.Exit(1)
}
builder := build.NewMainBuilder(
cfg,
rs,
)
var res *build.BuildResult var res *build.BuildResult
var scriptArgs *build.BuildPackageFromScriptArgs switch {
var dbArgs *build.BuildPackageFromDbArgs case c.IsSet("script"):
script = c.String("script")
packages = append(packages, c.String("script-package"))
buildArgs := &build.BuildArgs{ res, err = builder.BuildPackageFromScript(
ctx,
&build.BuildPackageFromScriptArgs{
Script: script,
Packages: packages,
BuildArgs: build.BuildArgs{
Opts: &types.BuildOpts{ Opts: &types.BuildOpts{
Clean: c.Bool("clean"), Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"), Interactive: c.Bool("interactive"),
}, },
PkgFormat_: build.GetPkgFormat(deps.Manager), PkgFormat_: build.GetPkgFormat(mgr),
Info: deps.Info, Info: info,
} },
},
switch { )
case c.IsSet("script"):
script, err = filepath.Abs(c.String("script"))
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Cannot get absolute script path"), err) slog.Error(gotext.Get("Error building package"), "err", err)
} os.Exit(1)
packages = append(packages, c.String("script-package"))
scriptArgs = &build.BuildPackageFromScriptArgs{
Script: script,
Packages: packages,
BuildArgs: *buildArgs,
} }
case c.IsSet("package"): case c.IsSet("package"):
// TODO: handle multiple packages // TODO: handle multiple packages
@ -137,97 +191,51 @@ func BuildCmd() *cli.Command {
packageSearch = arr[0] packageSearch = arr[0]
} }
pkgs, _, err := deps.Repos.FindPkgs(ctx, []string{packageSearch}) pkgs, _, _ := rs.FindPkgs(ctx, []string{packageSearch})
if err != nil { pkg, ok := pkgs[packageSearch]
return cliutils.FormatCliExit("failed to find pkgs", err) if len(pkg) < 1 || !ok {
} slog.Error(gotext.Get("Package not found"))
os.Exit(1)
pkg := cliutils.FlattenPkgs(ctx, pkgs, "build", c.Bool("interactive"))
if len(pkg) < 1 {
return cliutils.FormatCliExit(gotext.Get("Package not found"), nil)
} }
if pkg[0].BasePkgName != "" { if pkg[0].BasePkgName != "" {
packages = append(packages, pkg[0].Name) packages = append(packages, pkg[0].Name)
} }
dbArgs = &build.BuildPackageFromDbArgs{
Package: &pkg[0],
Packages: packages,
BuildArgs: *buildArgs,
}
default:
return cliutils.FormatCliExit(gotext.Get("Nothing to build"), nil)
}
if scriptArgs != nil {
scriptFile := filepath.Base(scriptArgs.Script)
newScriptDir, scriptDirCleanup, err := Mount(filepath.Dir(scriptArgs.Script))
if err != nil {
return err
}
defer scriptDirCleanup()
scriptArgs.Script = filepath.Join(newScriptDir, scriptFile)
}
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
installer, installerClose, err := build.GetSafeInstaller()
if err != nil {
return err
}
defer installerClose()
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
return err
}
scripter, scripterClose, err := build.GetSafeScriptExecutor()
if err != nil {
return err
}
defer scripterClose()
builder, err := build.NewMainBuilder(
deps.Cfg,
deps.Manager,
deps.Repos,
scripter,
installer,
)
if err != nil {
return err
}
if scriptArgs != nil {
res, err = builder.BuildPackageFromScript(
ctx,
scriptArgs,
)
} else if dbArgs != nil {
res, err = builder.BuildPackageFromDb( res, err = builder.BuildPackageFromDb(
ctx, ctx,
dbArgs, &build.BuildPackageFromDbArgs{
Package: &pkg[0],
Packages: packages,
BuildArgs: build.BuildArgs{
Opts: &types.BuildOpts{
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
},
PkgFormat_: build.GetPkgFormat(mgr),
Info: info,
},
},
) )
}
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error building package"), err) slog.Error(gotext.Get("Error building package"), "err", err)
os.Exit(1)
}
default:
slog.Error(gotext.Get("Nothing to build"))
os.Exit(1)
} }
// Перемещение собранных пакетов в рабочую директорию
for _, pkgPath := range res.PackagePaths { for _, pkgPath := range res.PackagePaths {
name := filepath.Base(pkgPath) name := filepath.Base(pkgPath)
err = osutils.Move(pkgPath, filepath.Join(wd, name)) err = osutils.Move(pkgPath, filepath.Join(wd, name))
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err) slog.Error(gotext.Get("Error moving the package"), "err", err)
os.Exit(1)
} }
} }
slog.Info(gotext.Get("Done"))
return nil return nil
}, },
} }

17
fix.go

@ -27,7 +27,6 @@ import (
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
) )
@ -37,7 +36,7 @@ func FixCmd() *cli.Command {
Name: "fix", Name: "fix",
Usage: gotext.Get("Attempt to fix problems with ALR"), Usage: gotext.Get("Attempt to fix problems with ALR"),
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err return err
} }
@ -61,19 +60,22 @@ func FixCmd() *cli.Command {
dir, err := os.Open(paths.CacheDir) dir, err := os.Open(paths.CacheDir)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err) slog.Error(gotext.Get("Unable to open cache directory"))
return cli.Exit(err, 1)
} }
defer dir.Close() defer dir.Close()
entries, err := dir.Readdirnames(-1) entries, err := dir.Readdirnames(-1)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err) slog.Error(gotext.Get("Unable to read cache directory contents"))
return cli.Exit(err, 1)
} }
for _, entry := range entries { for _, entry := range entries {
err = os.RemoveAll(filepath.Join(paths.CacheDir, entry)) err = os.RemoveAll(filepath.Join(paths.CacheDir, entry))
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), err) slog.Error(gotext.Get("Unable to remove cache item"), "item", entry)
return cli.Exit(err, 1)
} }
} }
@ -81,14 +83,15 @@ func FixCmd() *cli.Command {
err = os.MkdirAll(paths.CacheDir, 0o755) err = os.MkdirAll(paths.CacheDir, 0o755)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err) slog.Error(gotext.Get("Unable to create new cache directory"))
return cli.Exit(err, 1)
} }
deps, err = appbuilder. deps, err = appbuilder.
New(ctx). New(ctx).
WithConfig(). WithConfig().
WithDB(). WithDB().
WithReposForcePull(). WithRepos().
Build() Build()
if err != nil { if err != nil {
return cli.Exit(err, 1) return cli.Exit(err, 1)

4
go.mod

@ -4,6 +4,8 @@ go 1.22
toolchain go1.23.5 toolchain go1.23.5
replace github.com/creack/pty => github.com/creack/pty v1.1.19
require ( require (
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/PuerkitoBio/purell v1.2.0 github.com/PuerkitoBio/purell v1.2.0
@ -14,6 +16,7 @@ require (
github.com/charmbracelet/bubbletea v1.2.4 github.com/charmbracelet/bubbletea v1.2.4
github.com/charmbracelet/lipgloss v1.0.0 github.com/charmbracelet/lipgloss v1.0.0
github.com/charmbracelet/log v0.4.0 github.com/charmbracelet/log v0.4.0
github.com/creack/pty v1.1.24
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0 github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0
github.com/go-git/go-billy/v5 v5.5.0 github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.12.0 github.com/go-git/go-git/v5 v5.12.0
@ -65,7 +68,6 @@ require (
github.com/cloudflare/circl v1.3.8 // indirect github.com/cloudflare/circl v1.3.8 // indirect
github.com/connesc/cipherio v0.2.1 // indirect github.com/connesc/cipherio v0.2.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/creack/pty v1.1.24 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect

5
go.sum

@ -106,9 +106,8 @@ github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEyc
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= 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 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.19 h1:tUN6H7LWqNx4hQVxomd0CVsDwaDr9gaRQaI4GpSmrsA=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.19/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

@ -30,7 +30,6 @@ import (
"mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers" "gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
@ -72,17 +71,19 @@ func HelperCmd() *cli.Command {
helper, ok := helpers.Helpers[c.Args().First()] helper, ok := helpers.Helpers[c.Args().First()]
if !ok { if !ok {
slog.Error(gotext.Get("No such helper command"), "name", c.Args().First()) slog.Error(gotext.Get("No such helper command"), "name", c.Args().First())
return cli.Exit(gotext.Get("No such helper command"), 1) os.Exit(1)
} }
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err) slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
} }
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
} }
hc := interp.HandlerContext{ hc := interp.HandlerContext{

59
info.go

@ -21,6 +21,7 @@ package main
import ( import (
"fmt" "fmt"
"log/slog"
"os" "os"
"github.com/jeandeaual/go-locale" "github.com/jeandeaual/go-locale"
@ -30,6 +31,7 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" "gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
@ -47,25 +49,31 @@ func InfoCmd() *cli.Command {
Usage: gotext.Get("Show all information, not just for the current distro"), Usage: gotext.Get("Show all information, not just for the current distro"),
}, },
}, },
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { BashComplete: func(c *cli.Context) {
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err slog.Error("Can't drop caps")
os.Exit(1)
} }
ctx := c.Context ctx := c.Context
deps, err := appbuilder. cfg := config.New()
New(ctx). err := cfg.Load()
WithConfig().
WithDB().
Build()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
} }
defer deps.Defer()
result, err := deps.DB.GetPkgs(c.Context, "true") db := database.New(cfg)
err = db.Init(ctx)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
}
result, err := db.GetPkgs(c.Context, "true")
if err != nil {
slog.Error(gotext.Get("Error getting packages"), "err", err)
os.Exit(1)
} }
defer result.Close() defer result.Close()
@ -73,15 +81,15 @@ func InfoCmd() *cli.Command {
var pkg database.Package var pkg database.Package
err = result.StructScan(&pkg) err = result.StructScan(&pkg)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err) slog.Error(gotext.Get("Error iterating over packages"), "err", err)
os.Exit(1)
} }
fmt.Println(pkg.Name) fmt.Println(pkg.Name)
} }
return nil },
}),
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err return err
} }
@ -107,11 +115,13 @@ func InfoCmd() *cli.Command {
found, _, err := rs.FindPkgs(ctx, args.Slice()) found, _, err := rs.FindPkgs(ctx, args.Slice())
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error finding packages"), err) slog.Error(gotext.Get("Error finding packages"))
return cli.Exit(err, 1)
} }
if len(found) == 0 { if len(found) == 0 {
return cliutils.FormatCliExit(gotext.Get("Package not found"), err) slog.Error(gotext.Get("Package not found"))
return cli.Exit(err, 1)
} }
pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive")) pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive"))
@ -121,7 +131,8 @@ func InfoCmd() *cli.Command {
systemLang, err := locale.GetLanguage() systemLang, err := locale.GetLanguage()
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Can't detect system language"), err) slog.Error(gotext.Get("Can't detect system language"))
return cli.Exit(err, 1)
} }
if systemLang == "" { if systemLang == "" {
systemLang = "en" systemLang = "en"
@ -130,7 +141,8 @@ func InfoCmd() *cli.Command {
if !all { if !all {
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) slog.Error(gotext.Get("Error parsing os-release file"))
return cli.Exit(err, 1)
} }
names, err = overrides.Resolve( names, err = overrides.Resolve(
info, info,
@ -138,7 +150,8 @@ func InfoCmd() *cli.Command {
WithLanguages([]string{systemLang}), WithLanguages([]string{systemLang}),
) )
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err) slog.Error(gotext.Get("Error resolving overrides"))
return cli.Exit(err, 1)
} }
} }
@ -146,12 +159,14 @@ func InfoCmd() *cli.Command {
if !all { if !all {
err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names)) err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names))
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) slog.Error(gotext.Get("Error encoding script variables"))
return cli.Exit(err, 1)
} }
} else { } else {
err = yaml.NewEncoder(os.Stdout).Encode(pkg) err = yaml.NewEncoder(os.Stdout).Encode(pkg)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) slog.Error(gotext.Get("Error encoding script variables"))
return cli.Exit(err, 1)
} }
} }

@ -21,17 +21,20 @@ package main
import ( import (
"fmt" "fmt"
"log/slog"
"os"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
func InstallCmd() *cli.Command { func InstallCmd() *cli.Command {
@ -47,59 +50,58 @@ func InstallCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfNotRoot(); err != nil { ctx := c.Context
return err
}
args := c.Args() args := c.Args()
if args.Len() < 1 { if args.Len() < 1 {
return cliutils.FormatCliExit(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()), nil) slog.Error(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()))
os.Exit(1)
} }
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { mgr := manager.Detect()
return err if mgr == nil {
slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
} }
installer, installerClose, err := build.GetSafeInstaller() cfg := config.New()
err := cfg.Load()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error loading config"), "err", err)
} os.Exit(1)
defer installerClose()
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
return err
} }
scripter, scripterClose, err := build.GetSafeScriptExecutor() db := database.New(cfg)
rs := repos.New(cfg, db)
err = db.Init(ctx)
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
} }
defer scripterClose()
ctx := c.Context err = utils.DropCapsToAlrUser()
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
WithRepos().
WithDistroInfo().
WithManager().
Build()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error dropping capabilities"), "err", err)
os.Exit(1)
} }
defer deps.Defer()
builder, err := build.NewMainBuilder( builder := build.NewMainBuilder(
deps.Cfg, cfg,
deps.Manager, rs,
deps.Repos,
scripter,
installer,
) )
if cfg.AutoPull() {
err := rs.Pull(ctx, cfg.Repos())
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error pulling repositories"), "err", err)
os.Exit(1)
}
}
info, err := distro.ParseOSRelease(ctx)
if err != nil {
slog.Error(gotext.Get("Error parsing os release"), "err", err)
os.Exit(1)
} }
err = builder.InstallPkgs( err = builder.InstallPkgs(
@ -109,36 +111,36 @@ func InstallCmd() *cli.Command {
Clean: c.Bool("clean"), Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"), Interactive: c.Bool("interactive"),
}, },
Info: deps.Info, Info: info,
PkgFormat_: build.GetPkgFormat(deps.Manager), PkgFormat_: build.GetPkgFormat(mgr),
}, },
args.Slice(), args.Slice(),
) )
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error parsing os release"), err) slog.Error(gotext.Get("Error parsing os release"), "err", err)
os.Exit(1)
} }
return nil return nil
}, },
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { BashComplete: func(c *cli.Context) {
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { cfg := config.New()
return err err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
} }
ctx := c.Context db := database.New(cfg)
deps, err := appbuilder. err = db.Init(c.Context)
New(ctx).
WithConfig().
WithDB().
Build()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
} }
defer deps.Defer() result, err := db.GetPkgs(c.Context, "true")
result, err := deps.DB.GetPkgs(c.Context, "true")
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) slog.Error(gotext.Get("Error getting packages"), "err", err)
os.Exit(1)
} }
defer result.Close() defer result.Close()
@ -146,14 +148,13 @@ func InstallCmd() *cli.Command {
var pkg database.Package var pkg database.Package
err = result.StructScan(&pkg) err = result.StructScan(&pkg)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err) slog.Error(gotext.Get("Error iterating over packages"), "err", err)
os.Exit(1)
} }
fmt.Println(pkg.Name) fmt.Println(pkg.Name)
} }
},
return nil
}),
} }
} }
@ -162,24 +163,31 @@ func RemoveCmd() *cli.Command {
Name: "remove", Name: "remove",
Usage: gotext.Get("Remove an installed package"), Usage: gotext.Get("Remove an installed package"),
Aliases: []string{"rm"}, Aliases: []string{"rm"},
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { BashComplete: func(c *cli.Context) {
ctx := c.Context cfg := config.New()
err := cfg.Load()
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
WithManager().
Build()
if err != nil { if err != nil {
return cli.Exit(err, 1) slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg)
err = db.Init(c.Context)
if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
} }
defer deps.Defer()
installedAlrPackages := map[string]string{} installedAlrPackages := map[string]string{}
installed, err := deps.Manager.ListInstalled(&manager.Opts{AsRoot: false}) mgr := manager.Detect()
if mgr == nil {
slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
}
installed, err := mgr.ListInstalled(&manager.Opts{AsRoot: false})
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error listing installed packages"), err) slog.Error(gotext.Get("Error listing installed packages"), "err", err)
os.Exit(1)
} }
for pkgName, version := range installed { for pkgName, version := range installed {
matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName) matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName)
@ -190,9 +198,10 @@ func RemoveCmd() *cli.Command {
} }
} }
result, err := deps.DB.GetPkgs(c.Context, "true") result, err := db.GetPkgs(c.Context, "true")
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) slog.Error(gotext.Get("Error getting packages"), "err", err)
os.Exit(1)
} }
defer result.Close() defer result.Close()
@ -200,7 +209,8 @@ func RemoveCmd() *cli.Command {
var pkg database.Package var pkg database.Package
err = result.StructScan(&pkg) err = result.StructScan(&pkg)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err) slog.Error(gotext.Get("Error iterating over packages"), "err", err)
os.Exit(1)
} }
_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)] _, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)]
@ -210,33 +220,27 @@ func RemoveCmd() *cli.Command {
fmt.Println(pkg.Name) fmt.Println(pkg.Name)
} }
},
return nil
}),
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfNotRoot(); err != nil {
return err
}
args := c.Args() args := c.Args()
if args.Len() < 1 { if args.Len() < 1 {
return cliutils.FormatCliExit(gotext.Get("Command remove expected at least 1 argument, got %d", args.Len()), nil) slog.Error(gotext.Get("Command remove expected at least 1 argument, got %d", args.Len()))
os.Exit(1)
} }
deps, err := appbuilder. mgr := manager.Detect()
New(c.Context). if mgr == nil {
WithManager(). slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
Build() os.Exit(1)
if err != nil {
return err
} }
defer deps.Defer()
if err := deps.Manager.Remove(&manager.Opts{ err := mgr.Remove(&manager.Opts{
AsRoot: true, AsRoot: true,
NoConfirm: !c.Bool("interactive"), NoConfirm: !c.Bool("interactive"),
}, c.Args().Slice()...); err != nil { }, c.Args().Slice()...)
return cliutils.FormatCliExit(gotext.Get("Error removing packages"), err) if err != nil {
slog.Error(gotext.Get("Error removing packages"), "err", err)
os.Exit(1)
} }
return nil return nil

@ -17,14 +17,13 @@
package main package main
import ( import (
"bufio"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"os" "os"
"os/exec" "os/exec"
"os/user" "os/user"
"path/filepath" "path/filepath"
"strings"
"syscall" "syscall"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
@ -32,14 +31,13 @@ import (
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"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/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" "gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
func InternalBuildCmd() *cli.Command { func InternalBuildCmd() *cli.Command {
@ -49,17 +47,16 @@ func InternalBuildCmd() *cli.Command {
Hidden: true, Hidden: true,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
logger.SetupForGoPlugin() logger.SetupForGoPlugin()
err := utils.DropCapsToAlrUser()
slog.Debug("start _internal-safe-script-executor", "uid", syscall.Getuid(), "gid", syscall.Getgid())
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
cfg := config.New()
err := cfg.Load()
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error loading config"), err) slog.Error("aa", "err", err)
os.Exit(1)
}
cfg := config.New()
err = cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
} }
logger := hclog.New(&hclog.LoggerOptions{ logger := hclog.New(&hclog.LoggerOptions{
@ -69,7 +66,6 @@ func InternalBuildCmd() *cli.Command {
JSONFormat: false, JSONFormat: false,
DisableTime: true, DisableTime: true,
}) })
plugin.Serve(&plugin.ServeConfig{ plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: build.HandshakeConfig, HandshakeConfig: build.HandshakeConfig,
Plugins: map[string]plugin.Plugin{ Plugins: map[string]plugin.Plugin{
@ -91,28 +87,26 @@ func InternalInstallCmd() *cli.Command {
Hidden: true, Hidden: true,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
logger.SetupForGoPlugin() logger.SetupForGoPlugin()
err := syscall.Setuid(0)
if err := utils.EnsureIsAlrUser(); err != nil {
return err
}
// Before escalating the rights, we made sure that
// this is an ALR user, so it looks safe.
err := utils.EscalateToRootUid()
if err != nil { if err != nil {
return cliutils.FormatCliExit("cannot escalate to root", err) slog.Error("err")
os.Exit(1)
} }
deps, err := appbuilder. cfg := config.New()
New(c.Context). err = cfg.Load()
WithConfig().
WithDB().
WithReposNoPull().
Build()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg)
rs := repos.New(cfg, db)
err = db.Init(c.Context)
if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
} }
defer deps.Defer()
logger := hclog.New(&hclog.LoggerOptions{ logger := hclog.New(&hclog.LoggerOptions{
Name: "plugin", Name: "plugin",
@ -127,6 +121,7 @@ func InternalInstallCmd() *cli.Command {
Plugins: map[string]plugin.Plugin{ Plugins: map[string]plugin.Plugin{
"installer": &build.InstallerPlugin{ "installer": &build.InstallerPlugin{
Impl: build.NewInstaller( Impl: build.NewInstaller(
rs,
manager.Detect(), manager.Detect(),
), ),
}, },
@ -138,111 +133,52 @@ func InternalInstallCmd() *cli.Command {
} }
} }
func Mount(target string) (string, func(), error) {
exe, err := os.Executable()
if err != nil {
return "", nil, fmt.Errorf("failed to get executable path: %w", err)
}
cmd := exec.Command(exe, "_internal-temporary-mount", target)
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return "", nil, fmt.Errorf("failed to get stdout pipe: %w", err)
}
stdinPipe, err := cmd.StdinPipe()
if err != nil {
return "", nil, fmt.Errorf("failed to get stdin pipe: %w", err)
}
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
return "", nil, fmt.Errorf("failed to start mount: %w", err)
}
scanner := bufio.NewScanner(stdoutPipe)
var mountPath string
if scanner.Scan() {
mountPath = scanner.Text()
}
if err := scanner.Err(); err != nil {
_ = cmd.Process.Kill()
return "", nil, fmt.Errorf("failed to read mount output: %w", err)
}
if mountPath == "" {
_ = cmd.Process.Kill()
return "", nil, errors.New("mount failed: no target path returned")
}
cleanup := func() {
slog.Debug("cleanup triggered")
_, _ = fmt.Fprintln(stdinPipe, "")
_ = cmd.Wait()
}
return mountPath, cleanup, nil
}
func InternalMountCmd() *cli.Command { func InternalMountCmd() *cli.Command {
return &cli.Command{ return &cli.Command{
Name: "_internal-temporary-mount", Name: "_internal-mount",
HideHelp: true, HideHelp: true,
Hidden: true, Hidden: true,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
logger.SetupForGoPlugin()
sourceDir := c.Args().First() sourceDir := c.Args().First()
u, err := user.Current() u, _ := user.Current()
logger.SetupForGoPlugin()
err := syscall.Setuid(0)
if err != nil { if err != nil {
return cliutils.FormatCliExit("cannot get current user", err) slog.Error("Failed to setuid(0)", "err", err)
os.Exit(1)
} }
_, alrGid, err := utils.GetUidGidAlrUser() alrRunDir := "/var/run/alr"
err = os.MkdirAll(alrRunDir, 0o750)
if err != nil { if err != nil {
return cliutils.FormatCliExit("cannot get alr user", err) slog.Error("Error creating /var/run/alr directory", "err", err)
os.Exit(1)
} }
if _, err := os.Stat(sourceDir); err != nil { _, gid, _ := utils.GetUidGidAlrUser()
return cliutils.FormatCliExit(fmt.Sprintf("cannot read %s", sourceDir), err)
// Меняем группу на alr и права
err = os.Chown(alrRunDir, 0, gid) // root:alr
if err != nil {
slog.Error("Failed to chown /var/run/alr", "err", err)
os.Exit(1)
} }
if err := utils.EnuseIsPrivilegedGroupMember(); err != nil { // Создаем поддиректорию для bindfs
return err targetDir := filepath.Join(alrRunDir, fmt.Sprintf("bindfs-%d", os.Getpid()))
err = os.MkdirAll(targetDir, 0o750) // 0750: владелец (root) и группа (alr) имеют доступ
if err != nil {
slog.Error("Error creating bindfs target directory", "err", err)
os.Exit(1)
} }
// Before escalating the rights, we made sure that // Устанавливаем владельца и группу (root:alr)
// 1. user in wheel group err = os.Chown(targetDir, 0, gid)
// 2. user can access sourceDir if err != nil {
if err := utils.EscalateToRootUid(); err != nil { slog.Error("Failed to chown bindfs directory", "err", err)
return err os.Exit(1)
}
if err := syscall.Setgid(alrGid); err != nil {
return err
}
if err := os.MkdirAll(constants.AlrRunDir, 0o770); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("failed to create %s", constants.AlrRunDir), err)
}
if err := os.Chown(constants.AlrRunDir, 0, alrGid); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("failed to chown %s", constants.AlrRunDir), err)
}
targetDir := filepath.Join(constants.AlrRunDir, fmt.Sprintf("bindfs-%d", os.Getpid()))
// 0750: owner (root) and group (alr)
if err := os.MkdirAll(targetDir, 0o750); err != nil {
return cliutils.FormatCliExit("error creating bindfs target directory", err)
}
// chown AlrRunDir/mounts/bindfs-* to (root:alr),
// so alr user can access dir
if err := os.Chown(targetDir, 0, alrGid); err != nil {
return cliutils.FormatCliExit("failed to chown bindfs directory", err)
} }
bindfsCmd := exec.Command( bindfsCmd := exec.Command(
@ -254,24 +190,74 @@ func InternalMountCmd() *cli.Command {
bindfsCmd.Stderr = os.Stderr bindfsCmd.Stderr = os.Stderr
if err := bindfsCmd.Run(); err != nil { if err := bindfsCmd.Start(); err != nil {
return cliutils.FormatCliExit("failed to strart bindfs", err) slog.Error("Error starting bindfs", "err", err)
os.Exit(1)
} }
fmt.Println(targetDir) fmt.Print(targetDir)
_, _ = bufio.NewReader(os.Stdin).ReadString('\n') return nil
},
}
}
slog.Debug("start unmount", "dir", targetDir) func InternalUnmountCmd() *cli.Command {
return &cli.Command{
Name: "_internal-umount",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
currentUser, err := user.Current()
if err != nil {
slog.Error("Failed to get current user", "err", err)
os.Exit(1)
}
uid, gid, err := utils.GetUidGidAlrUserString()
if err != nil {
slog.Error("Failed to get alr user info", "err", err)
os.Exit(1)
}
if currentUser.Uid != uid && currentUser.Gid != gid {
slog.Error("Only alr user can unmount these directories")
os.Exit(1)
}
targetDir := c.Args().First()
if targetDir == "" {
slog.Error("No target directory specified")
os.Exit(1)
}
if !strings.HasPrefix(targetDir, "/var/run/alr/") {
slog.Error("Can only unmount directories under /var/run/alr")
os.Exit(1)
}
if _, err := os.Stat(targetDir); os.IsNotExist(err) {
slog.Error("Target directory does not exist", "dir", targetDir)
os.Exit(1)
}
err = syscall.Setuid(0)
if err != nil {
slog.Error("Failed to setuid(0)", "err", err)
os.Exit(1)
}
umountCmd := exec.Command("umount", targetDir) umountCmd := exec.Command("umount", targetDir)
umountCmd.Stderr = os.Stderr umountCmd.Stderr = os.Stderr
if err := umountCmd.Run(); err != nil { if err := umountCmd.Run(); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("failed to unmount %s", targetDir), err) slog.Error("Error unmounting directory", "dir", targetDir, "err", err)
os.Exit(1)
} }
if err := os.Remove(targetDir); err != nil { if err := os.Remove(targetDir); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("error removing directory %s", targetDir), err) slog.Error("Error removing directory", "dir", targetDir, "err", err)
os.Exit(1)
} }
return nil return nil

@ -22,12 +22,10 @@ import (
"log/slog" "log/slog"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
@ -35,8 +33,6 @@ type AppDeps struct {
Cfg *config.ALRConfig Cfg *config.ALRConfig
DB *db.Database DB *db.Database
Repos *repos.Repos Repos *repos.Repos
Info *distro.OSRelease
Manager manager.Manager
} }
func (d *AppDeps) Defer() { func (d *AppDeps) Defer() {
@ -57,14 +53,6 @@ func New(ctx context.Context) *AppBuilder {
return &AppBuilder{ctx: ctx} return &AppBuilder{ctx: ctx}
} }
func (b *AppBuilder) UseConfig(cfg *config.ALRConfig) *AppBuilder {
if b.err != nil {
return b
}
b.deps.Cfg = cfg
return b
}
func (b *AppBuilder) WithConfig() *AppBuilder { func (b *AppBuilder) WithConfig() *AppBuilder {
if b.err != nil { if b.err != nil {
return b return b
@ -72,7 +60,8 @@ func (b *AppBuilder) WithConfig() *AppBuilder {
cfg := config.New() cfg := config.New()
if err := cfg.Load(); err != nil { if err := cfg.Load(); err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error loading config"), err) slog.Error(gotext.Get("Error loading config"), "err", err)
b.err = cli.Exit("", 1)
return b return b
} }
@ -93,7 +82,8 @@ func (b *AppBuilder) WithDB() *AppBuilder {
db := db.New(cfg) db := db.New(cfg)
if err := db.Init(b.ctx); err != nil { if err := db.Init(b.ctx); err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error initialization database"), err) slog.Error(gotext.Get("Error initialization database"), "err", err)
b.err = cli.Exit("", 1)
return b return b
} }
@ -102,21 +92,6 @@ func (b *AppBuilder) WithDB() *AppBuilder {
} }
func (b *AppBuilder) WithRepos() *AppBuilder { func (b *AppBuilder) WithRepos() *AppBuilder {
b.withRepos(true, false)
return b
}
func (b *AppBuilder) WithReposForcePull() *AppBuilder {
b.withRepos(true, true)
return b
}
func (b *AppBuilder) WithReposNoPull() *AppBuilder {
b.withRepos(false, false)
return b
}
func (b *AppBuilder) withRepos(enablePull, forcePull bool) *AppBuilder {
if b.err != nil { if b.err != nil {
return b return b
} }
@ -130,9 +105,10 @@ func (b *AppBuilder) withRepos(enablePull, forcePull bool) *AppBuilder {
rs := repos.New(cfg, db) rs := repos.New(cfg, db)
if enablePull && (forcePull || cfg.AutoPull()) { if cfg.AutoPull() {
if err := rs.Pull(b.ctx, cfg.Repos()); err != nil { if err := rs.Pull(b.ctx, cfg.Repos()); err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error pulling repositories"), err) slog.Error(gotext.Get("Error pulling repositories"), "err", err)
b.err = cli.Exit("", 1)
return b return b
} }
} }
@ -142,32 +118,6 @@ func (b *AppBuilder) withRepos(enablePull, forcePull bool) *AppBuilder {
return b return b
} }
func (b *AppBuilder) WithDistroInfo() *AppBuilder {
if b.err != nil {
return b
}
b.deps.Info, b.err = distro.ParseOSRelease(b.ctx)
if b.err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error parsing os release"), b.err)
}
return b
}
func (b *AppBuilder) WithManager() *AppBuilder {
if b.err != nil {
return b
}
b.deps.Manager = manager.Detect()
if b.deps.Manager == nil {
b.err = cliutils.FormatCliExit(gotext.Get("Unable to detect a supported package manager on the system"), nil)
}
return b
}
func (b *AppBuilder) Build() (*AppDeps, error) { func (b *AppBuilder) Build() (*AppDeps, error) {
if b.err != nil { if b.err != nil {
return nil, b.err return nil, b.err

@ -1,60 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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 <http://www.gnu.org/licenses/>.
package cliutils
import (
"errors"
"fmt"
"log/slog"
"github.com/urfave/cli/v2"
)
type BashCompleteWithErrorFunc func(c *cli.Context) error
func BashCompleteWithError(f BashCompleteWithErrorFunc) cli.BashCompleteFunc {
return func(c *cli.Context) { HandleExitCoder(f(c)) }
}
func HandleExitCoder(err error) {
if err == nil {
return
}
if exitErr, ok := err.(cli.ExitCoder); ok {
if err.Error() != "" {
if _, ok := exitErr.(cli.ErrorFormatter); ok {
slog.Error(fmt.Sprintf("%+v\n", err))
} else {
slog.Error(err.Error())
}
}
cli.OsExiter(exitErr.ExitCode())
return
}
}
func FormatCliExit(msg string, err error) cli.ExitCoder {
return FormatCliExitWithCode(msg, err, 1)
}
func FormatCliExitWithCode(msg string, err error, exitCode int) cli.ExitCoder {
if err == nil {
return cli.Exit(errors.New(msg), exitCode)
}
return cli.Exit(fmt.Errorf("%s: %w", msg, err), exitCode)
}

@ -28,7 +28,6 @@ import (
"github.com/caarlos0/env" "github.com/caarlos0/env"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
) )
@ -84,9 +83,14 @@ func mergeStructs(dst, src interface{}) {
} }
} }
const (
systemConfigPath = "/etc/alr/alr.toml"
systemCachePath = "/var/cache/alr"
)
func (c *ALRConfig) Load() error { func (c *ALRConfig) Load() error {
systemConfig, err := readConfig( systemConfig, err := readConfig(
constants.SystemConfigPath, systemConfigPath,
) )
if err != nil { if err != nil {
slog.Debug("Cannot read system config", "err", err) slog.Debug("Cannot read system config", "err", err)
@ -104,8 +108,8 @@ func (c *ALRConfig) Load() error {
c.cfg = config c.cfg = config
c.paths = &Paths{} c.paths = &Paths{}
c.paths.UserConfigPath = constants.SystemConfigPath c.paths.UserConfigPath = systemConfigPath
c.paths.CacheDir = constants.SystemCachePath c.paths.CacheDir = systemCachePath
c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo") c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo")
c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs") c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs")
c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db") c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db")

@ -1,24 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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 <http://www.gnu.org/licenses/>.
package constants
const (
SystemConfigPath = "/etc/alr/alr.toml"
SystemCachePath = "/var/cache/alr"
AlrRunDir = "/var/run/alr"
PrivilegedGroup = "wheel"
)

@ -19,7 +19,6 @@ package logger
import ( import (
"io" "io"
"log" "log"
"strings"
chLog "github.com/charmbracelet/log" chLog "github.com/charmbracelet/log"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
@ -56,22 +55,7 @@ func (a *HCLoggerAdapter) Log(level hclog.Level, msg string, args ...interface{}
filteredArgs = append(filteredArgs, args[i], args[i+1]) filteredArgs = append(filteredArgs, args[i], args[i+1])
} }
} }
a.logger.l.Log(hclogLevelTochLog(level), msg, filteredArgs...)
// Start ugly hacks
// Ignore exit messages
// - https://github.com/hashicorp/go-plugin/issues/331
// - https://github.com/hashicorp/go-plugin/issues/203
// - https://github.com/hashicorp/go-plugin/issues/192
var chLogLevel chLog.Level
if msg == "plugin process exited" ||
msg == "[ERR] plugin: stream copy 'stderr' error: stream closed" ||
strings.HasPrefix(msg, "[DEBUG] plugin") {
chLogLevel = chLog.DebugLevel
} else {
chLogLevel = hclogLevelTochLog(level)
}
a.logger.l.Log(chLogLevel, msg, filteredArgs...)
} }
func (a *HCLoggerAdapter) Trace(msg string, args ...interface{}) { func (a *HCLoggerAdapter) Trace(msg string, args ...interface{}) {

@ -9,64 +9,76 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: build.go:42 #: build.go:47
msgid "Build a local package" msgid "Build a local package"
msgstr "" msgstr ""
#: build.go:48 #: build.go:53
msgid "Path to the build script" msgid "Path to the build script"
msgstr "" msgstr ""
#: build.go:53 #: build.go:58
msgid "Specify subpackage in script (for multi package script only)" msgid "Specify subpackage in script (for multi package script only)"
msgstr "" msgstr ""
#: build.go:58 #: build.go:63
msgid "Name of the package to build and its repo (example: default/go-bin)" msgid "Name of the package to build and its repo (example: default/go-bin)"
msgstr "" msgstr ""
#: build.go:63 #: build.go:68
msgid "" msgid ""
"Build package from scratch even if there's an already built package available" "Build package from scratch even if there's an already built package available"
msgstr "" msgstr ""
#: build.go:73 #: build.go:74 build.go:79 build.go:89 build.go:103
msgid "Error getting working directory" msgid "Error getting working directory"
msgstr "" msgstr ""
#: build.go:118 #: build.go:110 build.go:115
msgid "Cannot get absolute script path" msgid "Error dropping capabilities"
msgstr "" msgstr ""
#: build.go:148 #: build.go:123
msgid "Package not found" msgid "Error loading config"
msgstr "" msgstr ""
#: build.go:161 #: build.go:131
msgid "Nothing to build" msgid "Error initialization database"
msgstr "" msgstr ""
#: build.go:218 #: build.go:141
msgid "Unable to detect a supported package manager on the system"
msgstr ""
#: build.go:147
msgid "Error parsing os release"
msgstr ""
#: build.go:179 build.go:221
msgid "Error building package" msgid "Error building package"
msgstr "" msgstr ""
#: build.go:197
msgid "Package not found"
msgstr ""
#: build.go:225 #: build.go:225
msgid "Nothing to build"
msgstr ""
#: build.go:234
msgid "Error moving the package" msgid "Error moving the package"
msgstr "" msgstr ""
#: build.go:229 #: fix.go:37
msgid "Done"
msgstr ""
#: fix.go:38
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "" msgstr ""
#: fix.go:59 #: fix.go:58
msgid "Clearing cache directory" msgid "Clearing cache directory"
msgstr "" msgstr ""
#: fix.go:64 #: fix.go:63
msgid "Unable to open cache directory" msgid "Unable to open cache directory"
msgstr "" msgstr ""
@ -74,18 +86,22 @@ msgstr ""
msgid "Unable to read cache directory contents" msgid "Unable to read cache directory contents"
msgstr "" msgstr ""
#: fix.go:76 #: fix.go:77
msgid "Unable to remove cache item (%s)" msgid "Unable to remove cache item"
msgstr "" msgstr ""
#: fix.go:80 #: fix.go:82
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "" msgstr ""
#: fix.go:84 #: fix.go:86
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "" msgstr ""
#: fix.go:101
msgid "Done"
msgstr ""
#: gen.go:34 #: gen.go:34
msgid "Generate a ALR script from a template" msgid "Generate a ALR script from a template"
msgstr "" msgstr ""
@ -94,104 +110,88 @@ msgstr ""
msgid "Generate a ALR script for a pip module" msgid "Generate a ALR script for a pip module"
msgstr "" msgstr ""
#: helper.go:42 #: helper.go:41
msgid "List all the available helper commands" msgid "List all the available helper commands"
msgstr "" msgstr ""
#: helper.go:54 #: helper.go:53
msgid "Run a ALR helper command" msgid "Run a ALR helper command"
msgstr "" msgstr ""
#: helper.go:61 #: helper.go:60
msgid "The directory that the install commands will install to" msgid "The directory that the install commands will install to"
msgstr "" msgstr ""
#: helper.go:74 helper.go:75 #: helper.go:73
msgid "No such helper command" msgid "No such helper command"
msgstr "" msgstr ""
#: helper.go:85 #: info.go:44
msgid "Error parsing os-release file"
msgstr ""
#: info.go:42
msgid "Print information about a package" msgid "Print information about a package"
msgstr "" msgstr ""
#: info.go:47 #: info.go:49
msgid "Show all information, not just for the current distro" msgid "Show all information, not just for the current distro"
msgstr "" msgstr ""
#: info.go:68 #: info.go:75
msgid "Error getting packages" msgid "Error getting packages"
msgstr "" msgstr ""
#: info.go:76 #: info.go:84
msgid "Error iterating over packages" msgid "Error iterating over packages"
msgstr "" msgstr ""
#: info.go:90 #: info.go:98
msgid "Command info expected at least 1 argument, got %d" msgid "Command info expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: info.go:110 #: info.go:118
msgid "Error finding packages" msgid "Error finding packages"
msgstr "" msgstr ""
#: info.go:124 #: info.go:134
msgid "Can't detect system language" msgid "Can't detect system language"
msgstr "" msgstr ""
#: info.go:141 #: info.go:144
msgid "Error parsing os-release file"
msgstr ""
#: info.go:153
msgid "Error resolving overrides" msgid "Error resolving overrides"
msgstr "" msgstr ""
#: info.go:149 info.go:154 #: info.go:162 info.go:168
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "" msgstr ""
#: install.go:40 #: install.go:43
msgid "Install a new package" msgid "Install a new package"
msgstr "" msgstr ""
#: install.go:56 #: install.go:57
msgid "Command install expected at least 1 argument, got %d" msgid "Command install expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: install.go:118 #: install.go:96
msgid "Error parsing os release"
msgstr ""
#: install.go:163
msgid "Remove an installed package"
msgstr ""
#: install.go:182
msgid "Error listing installed packages"
msgstr ""
#: install.go:223
msgid "Command remove expected at least 1 argument, got %d"
msgstr ""
#: install.go:239
msgid "Error removing packages"
msgstr ""
#: internal/cliutils/app_builder/builder.go:75
msgid "Error loading config"
msgstr ""
#: internal/cliutils/app_builder/builder.go:96
msgid "Error initialization database"
msgstr ""
#: internal/cliutils/app_builder/builder.go:135
msgid "Error pulling repositories" msgid "Error pulling repositories"
msgstr "" msgstr ""
#: internal/cliutils/app_builder/builder.go:165 #: install.go:164
msgid "Unable to detect a supported package manager on the system" msgid "Remove an installed package"
msgstr ""
#: install.go:189
msgid "Error listing installed packages"
msgstr ""
#: install.go:227
msgid "Command remove expected at least 1 argument, got %d"
msgstr ""
#: install.go:242
msgid "Error removing packages"
msgstr "" msgstr ""
#: internal/cliutils/prompt.go:60 #: internal/cliutils/prompt.go:60
@ -311,18 +311,10 @@ msgstr ""
msgid "ERROR" msgid "ERROR"
msgstr "" msgstr ""
#: internal/utils/cmd.go:95 #: internal/utils/cmd.go:94
msgid "Error dropping capabilities"
msgstr ""
#: internal/utils/cmd.go:123
msgid "You need to be root to perform this action" msgid "You need to be root to perform this action"
msgstr "" msgstr ""
#: internal/utils/cmd.go:165
msgid "You need to be a %s member to perform this action"
msgstr ""
#: list.go:41 #: list.go:41
msgid "List ALR repo packages" msgid "List ALR repo packages"
msgstr "" msgstr ""
@ -331,35 +323,35 @@ msgstr ""
msgid "Print the current ALR version and exit" msgid "Print the current ALR version and exit"
msgstr "" msgstr ""
#: main.go:61 #: main.go:79
msgid "Arguments to be passed on to the package manager" msgid "Arguments to be passed on to the package manager"
msgstr "" msgstr ""
#: main.go:67 #: main.go:85
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "" msgstr ""
#: main.go:147 #: main.go:185
msgid "Show help" msgid "Show help"
msgstr "" msgstr ""
#: main.go:151 #: main.go:189
msgid "Error while running app" msgid "Error while running app"
msgstr "" msgstr ""
#: pkg/build/build.go:394 #: pkg/build/build.go:392
msgid "Building package" msgid "Building package"
msgstr "" msgstr ""
#: pkg/build/build.go:423 #: pkg/build/build.go:421
msgid "The checksums array must be the same length as sources" msgid "The checksums array must be the same length as sources"
msgstr "" msgstr ""
#: pkg/build/build.go:454 #: pkg/build/build.go:448
msgid "Downloading sources" msgid "Downloading sources"
msgstr "" msgstr ""
#: pkg/build/build.go:543 #: pkg/build/build.go:535
msgid "Installing dependencies" msgid "Installing dependencies"
msgstr "" msgstr ""
@ -427,47 +419,51 @@ msgid ""
"updating ALR if something doesn't work." "updating ALR if something doesn't work."
msgstr "" msgstr ""
#: repo.go:39 #: repo.go:41
msgid "Add a new repository" msgid "Add a new repository"
msgstr "" msgstr ""
#: repo.go:46 #: repo.go:48
msgid "Name of the new repo" msgid "Name of the new repo"
msgstr "" msgstr ""
#: repo.go:52 #: repo.go:54
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "" msgstr ""
#: repo.go:79 #: repo.go:92 repo.go:172
msgid "Repo %s already exists"
msgstr ""
#: repo.go:90 repo.go:167
msgid "Error saving config" msgid "Error saving config"
msgstr "" msgstr ""
#: repo.go:116 #: repo.go:97 repo.go:199
msgid "Can't drop privileges"
msgstr ""
#: repo.go:104 repo.go:110 repo.go:219
msgid "Error pulling repos"
msgstr ""
#: repo.go:122
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "" msgstr ""
#: repo.go:123 #: repo.go:129
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "" msgstr ""
#: repo.go:156 #: repo.go:158
msgid "Repo \"%s\" does not exist" msgid "Repo does not exist"
msgstr "" msgstr ""
#: repo.go:163 #: repo.go:166
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "" msgstr ""
#: repo.go:186 #: repo.go:183
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "" msgstr ""
#: repo.go:197 #: repo.go:195
msgid "Pull all repositories that have changed" msgid "Pull all repositories that have changed"
msgstr "" msgstr ""
@ -499,22 +495,22 @@ msgstr ""
msgid "Error while executing search" msgid "Error while executing search"
msgstr "" msgstr ""
#: search.go:104 #: search.go:105
msgid "Error parsing format template" msgid "Error parsing format template"
msgstr "" msgstr ""
#: search.go:112 #: search.go:114
msgid "Error executing template" msgid "Error executing template"
msgstr "" msgstr ""
#: upgrade.go:47 #: upgrade.go:48
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "" msgstr ""
#: upgrade.go:109 upgrade.go:126 #: upgrade.go:111 upgrade.go:129
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "" msgstr ""
#: upgrade.go:129 #: upgrade.go:133
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "" msgstr ""

@ -16,66 +16,80 @@ msgstr ""
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Gtranslator 47.1\n" "X-Generator: Gtranslator 47.1\n"
#: build.go:42 #: build.go:47
msgid "Build a local package" msgid "Build a local package"
msgstr "Сборка локального пакета" msgstr "Сборка локального пакета"
#: build.go:48 #: build.go:53
msgid "Path to the build script" msgid "Path to the build script"
msgstr "Путь к скрипту сборки" msgstr "Путь к скрипту сборки"
#: build.go:53 #: build.go:58
msgid "Specify subpackage in script (for multi package script only)" msgid "Specify subpackage in script (for multi package script only)"
msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)" msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)"
#: build.go:58 #: build.go:63
msgid "Name of the package to build and its repo (example: default/go-bin)" msgid "Name of the package to build and its repo (example: default/go-bin)"
msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)" msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)"
#: build.go:63 #: build.go:68
msgid "" msgid ""
"Build package from scratch even if there's an already built package available" "Build package from scratch even if there's an already built package available"
msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет" msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет"
#: build.go:73 #: build.go:74 build.go:79 build.go:89 build.go:103
msgid "Error getting working directory" msgid "Error getting working directory"
msgstr "Ошибка при получении рабочего каталога" msgstr "Ошибка при получении рабочего каталога"
#: build.go:118 #: build.go:110 build.go:115
msgid "Cannot get absolute script path" #, fuzzy
msgstr "" msgid "Error dropping capabilities"
msgstr "Ошибка при открытии базы данных"
#: build.go:148 #: build.go:123
#, fuzzy
msgid "Error loading config"
msgstr "Ошибка при кодировании конфигурации"
#: build.go:131
msgid "Error initialization database"
msgstr "Ошибка инициализации базы данных"
#: build.go:141
msgid "Unable to detect a supported package manager on the system"
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе"
#: build.go:147
msgid "Error parsing os release"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: build.go:179 build.go:221
msgid "Error building package"
msgstr "Ошибка при сборке пакета"
#: build.go:197
msgid "Package not found" msgid "Package not found"
msgstr "Пакет не найден" msgstr "Пакет не найден"
#: build.go:161 #: build.go:225
#, fuzzy #, fuzzy
msgid "Nothing to build" msgid "Nothing to build"
msgstr "Исполнение build()" msgstr "Исполнение build()"
#: build.go:218 #: build.go:234
msgid "Error building package"
msgstr "Ошибка при сборке пакета"
#: build.go:225
msgid "Error moving the package" msgid "Error moving the package"
msgstr "Ошибка при перемещении пакета" msgstr "Ошибка при перемещении пакета"
#: build.go:229 #: fix.go:37
msgid "Done"
msgstr "Сделано"
#: fix.go:38
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "Попытка устранить проблемы с ALR" msgstr "Попытка устранить проблемы с ALR"
#: fix.go:59 #: fix.go:58
#, fuzzy #, fuzzy
msgid "Clearing cache directory" msgid "Clearing cache directory"
msgstr "Удаление каталога кэша" msgstr "Удаление каталога кэша"
#: fix.go:64 #: fix.go:63
#, fuzzy #, fuzzy
msgid "Unable to open cache directory" msgid "Unable to open cache directory"
msgstr "Не удалось удалить каталог кэша" msgstr "Не удалось удалить каталог кэша"
@ -85,19 +99,23 @@ msgstr "Не удалось удалить каталог кэша"
msgid "Unable to read cache directory contents" msgid "Unable to read cache directory contents"
msgstr "Не удалось удалить каталог кэша" msgstr "Не удалось удалить каталог кэша"
#: fix.go:76 #: fix.go:77
#, fuzzy #, fuzzy
msgid "Unable to remove cache item (%s)" msgid "Unable to remove cache item"
msgstr "Не удалось удалить каталог кэша" msgstr "Не удалось удалить каталог кэша"
#: fix.go:80 #: fix.go:82
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "Восстановление кэша" msgstr "Восстановление кэша"
#: fix.go:84 #: fix.go:86
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "Не удалось создать новый каталог кэша" msgstr "Не удалось создать новый каталог кэша"
#: fix.go:101
msgid "Done"
msgstr "Сделано"
#: gen.go:34 #: gen.go:34
msgid "Generate a ALR script from a template" msgid "Generate a ALR script from a template"
msgstr "Генерация скрипта ALR из шаблона" msgstr "Генерация скрипта ALR из шаблона"
@ -106,107 +124,90 @@ msgstr "Генерация скрипта ALR из шаблона"
msgid "Generate a ALR script for a pip module" msgid "Generate a ALR script for a pip module"
msgstr "Генерация скрипта ALR для модуля pip" msgstr "Генерация скрипта ALR для модуля pip"
#: helper.go:42 #: helper.go:41
msgid "List all the available helper commands" msgid "List all the available helper commands"
msgstr "Список всех доступных вспомогательных команды" msgstr "Список всех доступных вспомогательных команды"
#: helper.go:54 #: helper.go:53
msgid "Run a ALR helper command" msgid "Run a ALR helper command"
msgstr "Запустить вспомогательную команду ALR" msgstr "Запустить вспомогательную команду ALR"
#: helper.go:61 #: helper.go:60
msgid "The directory that the install commands will install to" msgid "The directory that the install commands will install to"
msgstr "Каталог, в который будут устанавливать команды установки" msgstr "Каталог, в который будут устанавливать команды установки"
#: helper.go:74 helper.go:75 #: helper.go:73
msgid "No such helper command" msgid "No such helper command"
msgstr "Такой вспомогательной команды нет" msgstr "Такой вспомогательной команды нет"
#: helper.go:85 #: info.go:44
msgid "Error parsing os-release file"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: info.go:42
msgid "Print information about a package" msgid "Print information about a package"
msgstr "Отобразить информацию о пакете" msgstr "Отобразить информацию о пакете"
#: info.go:47 #: info.go:49
msgid "Show all information, not just for the current distro" msgid "Show all information, not just for the current distro"
msgstr "Показывать всю информацию, не только для текущего дистрибутива" msgstr "Показывать всю информацию, не только для текущего дистрибутива"
#: info.go:68 #: info.go:75
msgid "Error getting packages" msgid "Error getting packages"
msgstr "Ошибка при получении пакетов" msgstr "Ошибка при получении пакетов"
#: info.go:76 #: info.go:84
msgid "Error iterating over packages" msgid "Error iterating over packages"
msgstr "Ошибка при переборе пакетов" msgstr "Ошибка при переборе пакетов"
#: info.go:90 #: info.go:98
msgid "Command info expected at least 1 argument, got %d" msgid "Command info expected at least 1 argument, got %d"
msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d" msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d"
#: info.go:110 #: info.go:118
msgid "Error finding packages" msgid "Error finding packages"
msgstr "Ошибка при поиске пакетов" msgstr "Ошибка при поиске пакетов"
#: info.go:124 #: info.go:134
#, fuzzy #, fuzzy
msgid "Can't detect system language" msgid "Can't detect system language"
msgstr "Ошибка при парсинге языка системы" msgstr "Ошибка при парсинге языка системы"
#: info.go:141 #: info.go:144
msgid "Error parsing os-release file"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: info.go:153
msgid "Error resolving overrides" msgid "Error resolving overrides"
msgstr "Ошибка устранения переорпеделений" msgstr "Ошибка устранения переорпеделений"
#: info.go:149 info.go:154 #: info.go:162 info.go:168
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "Ошибка кодирования переменных скрита" msgstr "Ошибка кодирования переменных скрита"
#: install.go:40 #: install.go:43
msgid "Install a new package" msgid "Install a new package"
msgstr "Установить новый пакет" msgstr "Установить новый пакет"
#: install.go:56 #: install.go:57
msgid "Command install expected at least 1 argument, got %d" msgid "Command install expected at least 1 argument, got %d"
msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d" msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d"
#: install.go:118 #: install.go:96
msgid "Error parsing os release"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: install.go:163
msgid "Remove an installed package"
msgstr "Удалить установленный пакет"
#: install.go:182
msgid "Error listing installed packages"
msgstr "Ошибка при составлении списка установленных пакетов"
#: install.go:223
msgid "Command remove expected at least 1 argument, got %d"
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d"
#: install.go:239
msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов"
#: internal/cliutils/app_builder/builder.go:75
#, fuzzy
msgid "Error loading config"
msgstr "Ошибка при кодировании конфигурации"
#: internal/cliutils/app_builder/builder.go:96
msgid "Error initialization database"
msgstr "Ошибка инициализации базы данных"
#: internal/cliutils/app_builder/builder.go:135
msgid "Error pulling repositories" msgid "Error pulling repositories"
msgstr "Ошибка при извлечении репозиториев" msgstr "Ошибка при извлечении репозиториев"
#: internal/cliutils/app_builder/builder.go:165 #: install.go:164
msgid "Unable to detect a supported package manager on the system" msgid "Remove an installed package"
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" msgstr "Удалить установленный пакет"
#: install.go:189
msgid "Error listing installed packages"
msgstr "Ошибка при составлении списка установленных пакетов"
#: install.go:227
msgid "Command remove expected at least 1 argument, got %d"
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d"
#: install.go:242
msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов"
#: internal/cliutils/prompt.go:60 #: internal/cliutils/prompt.go:60
msgid "Would you like to view the build script for %s" msgid "Would you like to view the build script for %s"
@ -326,19 +327,10 @@ msgstr "%s %s загружается — %s/с\n"
msgid "ERROR" msgid "ERROR"
msgstr "ОШИБКА" msgstr "ОШИБКА"
#: internal/utils/cmd.go:95 #: internal/utils/cmd.go:94
#, fuzzy
msgid "Error dropping capabilities"
msgstr "Ошибка при открытии базы данных"
#: internal/utils/cmd.go:123
msgid "You need to be root to perform this action" msgid "You need to be root to perform this action"
msgstr "" msgstr ""
#: internal/utils/cmd.go:165
msgid "You need to be a %s member to perform this action"
msgstr ""
#: list.go:41 #: list.go:41
msgid "List ALR repo packages" msgid "List ALR repo packages"
msgstr "Список пакетов репозитория ALR" msgstr "Список пакетов репозитория ALR"
@ -347,35 +339,35 @@ msgstr "Список пакетов репозитория ALR"
msgid "Print the current ALR version and exit" msgid "Print the current ALR version and exit"
msgstr "Показать текущую версию ALR и выйти" msgstr "Показать текущую версию ALR и выйти"
#: main.go:61 #: main.go:79
msgid "Arguments to be passed on to the package manager" msgid "Arguments to be passed on to the package manager"
msgstr "Аргументы, которые будут переданы менеджеру пакетов" msgstr "Аргументы, которые будут переданы менеджеру пакетов"
#: main.go:67 #: main.go:85
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "Включение интерактивных вопросов и запросов" msgstr "Включение интерактивных вопросов и запросов"
#: main.go:147 #: main.go:185
msgid "Show help" msgid "Show help"
msgstr "Показать справку" msgstr "Показать справку"
#: main.go:151 #: main.go:189
msgid "Error while running app" msgid "Error while running app"
msgstr "Ошибка при запуске приложения" msgstr "Ошибка при запуске приложения"
#: pkg/build/build.go:394 #: pkg/build/build.go:392
msgid "Building package" msgid "Building package"
msgstr "Сборка пакета" msgstr "Сборка пакета"
#: pkg/build/build.go:423 #: pkg/build/build.go:421
msgid "The checksums array must be the same length as sources" msgid "The checksums array must be the same length as sources"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники" msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
#: pkg/build/build.go:454 #: pkg/build/build.go:448
msgid "Downloading sources" msgid "Downloading sources"
msgstr "Скачивание источников" msgstr "Скачивание источников"
#: pkg/build/build.go:543 #: pkg/build/build.go:535
msgid "Installing dependencies" msgid "Installing dependencies"
msgstr "Установка зависимостей" msgstr "Установка зависимостей"
@ -449,50 +441,52 @@ msgstr ""
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте " "Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
"обновить ALR, если что-то не работает." "обновить ALR, если что-то не работает."
#: repo.go:39 #: repo.go:41
msgid "Add a new repository" msgid "Add a new repository"
msgstr "Добавить новый репозиторий" msgstr "Добавить новый репозиторий"
#: repo.go:46 #: repo.go:48
msgid "Name of the new repo" msgid "Name of the new repo"
msgstr "Название нового репозитория" msgstr "Название нового репозитория"
#: repo.go:52 #: repo.go:54
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "URL-адрес нового репозитория" msgstr "URL-адрес нового репозитория"
#: repo.go:79 #: repo.go:92 repo.go:172
#, fuzzy
msgid "Repo %s already exists"
msgstr "Репозитория не существует"
#: repo.go:90 repo.go:167
#, fuzzy #, fuzzy
msgid "Error saving config" msgid "Error saving config"
msgstr "Ошибка при кодировании конфигурации" msgstr "Ошибка при кодировании конфигурации"
#: repo.go:116 #: repo.go:97 repo.go:199
msgid "Can't drop privileges"
msgstr ""
#: repo.go:104 repo.go:110 repo.go:219
msgid "Error pulling repos"
msgstr "Ошибка при извлечении репозиториев"
#: repo.go:122
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "Удалить существующий репозиторий" msgstr "Удалить существующий репозиторий"
#: repo.go:123 #: repo.go:129
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "Название репозитория удалён" msgstr "Название репозитория удалён"
#: repo.go:156 #: repo.go:158
#, fuzzy msgid "Repo does not exist"
msgid "Repo \"%s\" does not exist"
msgstr "Репозитория не существует" msgstr "Репозитория не существует"
#: repo.go:163 #: repo.go:166
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "Ошибка при удалении каталога репозитория" msgstr "Ошибка при удалении каталога репозитория"
#: repo.go:186 #: repo.go:183
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "Ошибка при удалении пакетов из базы данных" msgstr "Ошибка при удалении пакетов из базы данных"
#: repo.go:197 #: repo.go:195
msgid "Pull all repositories that have changed" msgid "Pull all repositories that have changed"
msgstr "Скачать все изменённые репозитории" msgstr "Скачать все изменённые репозитории"
@ -525,37 +519,26 @@ msgstr "Формат выходных данных с использование
msgid "Error while executing search" msgid "Error while executing search"
msgstr "Ошибка при запуске приложения" msgstr "Ошибка при запуске приложения"
#: search.go:104 #: search.go:105
msgid "Error parsing format template" msgid "Error parsing format template"
msgstr "Ошибка при разборе шаблона" msgstr "Ошибка при разборе шаблона"
#: search.go:112 #: search.go:114
msgid "Error executing template" msgid "Error executing template"
msgstr "Ошибка при выполнении шаблона" msgstr "Ошибка при выполнении шаблона"
#: upgrade.go:47 #: upgrade.go:48
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "Обновить все установленные пакеты" msgstr "Обновить все установленные пакеты"
#: upgrade.go:109 upgrade.go:126 #: upgrade.go:111 upgrade.go:129
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "Ошибка при проверке обновлений" msgstr "Ошибка при проверке обновлений"
#: upgrade.go:129 #: upgrade.go:133
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "Здесь нечего делать." msgstr "Здесь нечего делать."
#~ msgid "Error pulling repos"
#~ msgstr "Ошибка при извлечении репозиториев"
#, fuzzy
#~ msgid "Error getting current executable"
#~ msgstr "Ошибка при получении рабочего каталога"
#, fuzzy
#~ msgid "Error mounting"
#~ msgstr "Ошибка при кодировании конфигурации"
#, fuzzy #, fuzzy
#~ msgid "Unable to create config directory" #~ msgid "Unable to create config directory"
#~ msgstr "Не удалось создать каталог конфигурации ALR" #~ msgstr "Не удалось создать каталог конфигурации ALR"

@ -18,6 +18,7 @@ package utils
import ( import (
"errors" "errors"
"log/slog"
"os" "os"
"os/user" "os/user"
"strconv" "strconv"
@ -25,9 +26,6 @@ import (
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
) )
func GetUidGidAlrUserString() (string, string, error) { func GetUidGidAlrUserString() (string, string, error) {
@ -70,66 +68,6 @@ func DropCapsToAlrUser() error {
if err != nil { if err != nil {
return err return err
} }
return EnsureIsAlrUser()
}
func ExitIfCantDropGidToAlr() cli.ExitCoder {
_, gid, err := GetUidGidAlrUser()
if err != nil {
return cliutils.FormatCliExit("cannot get gid alr", err)
}
err = syscall.Setgid(gid)
if err != nil {
return cliutils.FormatCliExit("cannot get setgid alr", err)
}
return nil
}
// ExitIfCantDropCapsToAlrUser attempts to drop capabilities to the already
// running user. Returns a cli.ExitCoder with an error if the operation fails.
// See also [ExitIfCantDropCapsToAlrUserNoPrivs] for a version that also applies
// no-new-privs.
func ExitIfCantDropCapsToAlrUser() cli.ExitCoder {
err := DropCapsToAlrUser()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error dropping capabilities"), err)
}
return nil
}
func ExitIfCantSetNoNewPrivs() cli.ExitCoder {
if err := NoNewPrivs(); err != nil {
return cliutils.FormatCliExit("error no new privs", err)
}
return nil
}
// ExitIfCantDropCapsToAlrUserNoPrivs combines [ExitIfCantDropCapsToAlrUser] with [ExitIfCantSetNoNewPrivs]
func ExitIfCantDropCapsToAlrUserNoPrivs() cli.ExitCoder {
if err := ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
if err := ExitIfCantSetNoNewPrivs(); err != nil {
return err
}
return nil
}
func ExitIfNotRoot() error {
if os.Getuid() != 0 {
return cli.Exit(gotext.Get("You need to be root to perform this action"), 1)
}
return nil
}
func EnsureIsAlrUser() error {
uid, gid, err := GetUidGidAlrUser()
if err != nil {
return err
}
newUid := syscall.Getuid() newUid := syscall.Getuid()
if newUid != uid { if newUid != uid {
return errors.New("new uid don't matches requested") return errors.New("new uid don't matches requested")
@ -141,46 +79,19 @@ func EnsureIsAlrUser() error {
return nil return nil
} }
func EnuseIsPrivilegedGroupMember() error { // Returns cli.Exit to
currentUser, err := user.Current() func ExitIfCantDropCapsToAlrUser() cli.ExitCoder {
err := DropCapsToAlrUser()
if err != nil { if err != nil {
return err slog.Debug("dropping capabilities error", "err", err)
} return cli.Exit(gotext.Get("Error dropping capabilities"), 1)
}
group, err := user.LookupGroup(constants.PrivilegedGroup) return nil
if err != nil { }
return err
} func ExitIfNotRoot() error {
if os.Getuid() != 0 {
groups, err := currentUser.GroupIds() return cli.Exit(gotext.Get("You need to be root to perform this action"), 1)
if err != nil {
return err
}
for _, gid := range groups {
if gid == group.Gid {
return nil
}
}
return cliutils.FormatCliExit(gotext.Get("You need to be a %s member to perform this action", constants.PrivilegedGroup), nil)
}
func EscalateToRootGid() error {
return syscall.Setgid(0)
}
func EscalateToRootUid() error {
return syscall.Setuid(0)
}
func EscalateToRoot() error {
err := EscalateToRootUid()
if err != nil {
return err
}
err = EscalateToRootGid()
if err != nil {
return err
} }
return nil return nil
} }

@ -1,23 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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 <http://www.gnu.org/licenses/>.
package utils
import "golang.org/x/sys/unix"
func NoNewPrivs() error {
return unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
}

19
list.go

@ -22,12 +22,12 @@ package main
import ( import (
"fmt" "fmt"
"log/slog" "log/slog"
"os"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
@ -47,7 +47,7 @@ func ListCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err return err
} }
@ -77,7 +77,8 @@ func ListCmd() *cli.Command {
result, err := db.GetPkgs(ctx, where, args...) result, err := db.GetPkgs(ctx, where, args...)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) slog.Error(gotext.Get("Error getting packages"), "err", err)
os.Exit(1)
} }
defer result.Close() defer result.Close()
@ -85,13 +86,14 @@ func ListCmd() *cli.Command {
if c.Bool("installed") { if c.Bool("installed") {
mgr := manager.Detect() mgr := manager.Detect()
if mgr == nil { if mgr == nil {
return cli.Exit(gotext.Get("Unable to detect a supported package manager on the system"), 1) slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
} }
installed, err := mgr.ListInstalled(&manager.Opts{AsRoot: false}) installed, err := mgr.ListInstalled(&manager.Opts{AsRoot: false})
if err != nil { if err != nil {
slog.Error(gotext.Get("Error listing installed packages"), "err", err) slog.Error(gotext.Get("Error listing installed packages"), "err", err)
return cli.Exit(err, 1) os.Exit(1)
} }
for pkgName, version := range installed { for pkgName, version := range installed {
@ -108,7 +110,7 @@ func ListCmd() *cli.Command {
var pkg database.Package var pkg database.Package
err := result.StructScan(&pkg) err := result.StructScan(&pkg)
if err != nil { if err != nil {
return cli.Exit(err, 1) return err
} }
if slices.Contains(cfg.IgnorePkgUpdates(), pkg.Name) { if slices.Contains(cfg.IgnorePkgUpdates(), pkg.Name) {
@ -128,6 +130,11 @@ func ListCmd() *cli.Command {
fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version) fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version)
} }
if err != nil {
slog.Error(gotext.Get("Error iterating over packages"), "err", err)
os.Exit(1)
}
return nil return nil
}, },
} }

46
main.go

@ -21,10 +21,10 @@ package main
import ( import (
"context" "context"
"fmt"
"log/slog" "log/slog"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
@ -50,6 +50,24 @@ func VersionCmd() *cli.Command {
} }
} }
func HandleExitCoder(err error) {
if err == nil {
return
}
if exitErr, ok := err.(cli.ExitCoder); ok {
if err.Error() != "" {
if _, ok := exitErr.(cli.ErrorFormatter); ok {
slog.Error(fmt.Sprintf("%+v\n", err))
} else {
slog.Error(err.Error())
}
}
cli.OsExiter(exitErr.ExitCode())
return
}
}
func GetApp() *cli.App { func GetApp() *cli.App {
return &cli.App{ return &cli.App{
Name: "alr", Name: "alr",
@ -82,21 +100,41 @@ func GetApp() *cli.App {
HelperCmd(), HelperCmd(),
VersionCmd(), VersionCmd(),
SearchCmd(), SearchCmd(),
// Internal commands // TEST
InternalBuildCmd(), InternalBuildCmd(),
InternalInstallCmd(), InternalInstallCmd(),
InternalMountCmd(), InternalMountCmd(),
InternalUnmountCmd(),
// InternalBuild2Cmd(),
}, },
Before: func(c *cli.Context) error { Before: func(c *cli.Context) error {
/*
cfg := config.New()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
/*
cmd := c.Args().First()
if cmd != "helper" && !cfg.AllowRunAsRoot() && os.Geteuid() == 0 {
slog.Error(gotext.Get("Running ALR as root is forbidden as it may cause catastrophic damage to your system"))
os.Exit(1)
}
if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" { if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" {
args := strings.Split(trimmed, " ") args := strings.Split(trimmed, " ")
manager.Args = append(manager.Args, args...) manager.Args = append(manager.Args, args...)
} }
return nil
*/
return nil return nil
}, },
EnableBashCompletion: true, EnableBashCompletion: true,
ExitErrHandler: func(cCtx *cli.Context, err error) { ExitErrHandler: func(c *cli.Context, err error) {
cliutils.HandleExitCoder(err)
}, },
} }
} }

@ -352,17 +352,16 @@ func (b *Builder) BuildPackage(
) (*BuildResult, error) { ) (*BuildResult, error) {
scriptPath := input.script scriptPath := input.script
slog.Debug("ReadScript")
sf, err := b.scriptExecutor.ReadScript(ctx, scriptPath) sf, err := b.scriptExecutor.ReadScript(ctx, scriptPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
slog.Debug("ExecuteFirstPass")
basePkg, varsOfPackages, err := b.scriptExecutor.ExecuteFirstPass(ctx, input, sf) basePkg, varsOfPackages, err := b.scriptExecutor.ExecuteFirstPass(ctx, input, sf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
slog.Debug("ExecuteFirstPass", "basePkg", basePkg, "varsOfPackages", varsOfPackages)
builtPaths := make([]string, 0) builtPaths := make([]string, 0)
@ -385,7 +384,6 @@ func (b *Builder) BuildPackage(
} }
} }
slog.Debug("ViewScript")
err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg) err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg)
if err != nil { if err != nil {
return nil, err return nil, err
@ -425,25 +423,21 @@ func (b *Builder) BuildPackage(
} }
sources, checksums = removeDuplicatesSources(sources, checksums) sources, checksums = removeDuplicatesSources(sources, checksums)
slog.Debug("installBuildDeps")
err = b.installBuildDeps(ctx, input, buildDepends) err = b.installBuildDeps(ctx, input, buildDepends)
if err != nil { if err != nil {
return nil, err return nil, err
} }
slog.Debug("installOptDeps")
err = b.installOptDeps(ctx, input, optDepends) err = b.installOptDeps(ctx, input, optDepends)
if err != nil { if err != nil {
return nil, err return nil, err
} }
slog.Debug("BuildALRDeps")
_, builtNames, repoDeps, err := b.BuildALRDeps(ctx, input, depends) _, builtNames, repoDeps, err := b.BuildALRDeps(ctx, input, depends)
if err != nil { if err != nil {
return nil, err return nil, err
} }
slog.Debug("PrepareDirs")
err = b.scriptExecutor.PrepareDirs(ctx, input, basePkg) err = b.scriptExecutor.PrepareDirs(ctx, input, basePkg)
if err != nil { if err != nil {
return nil, err return nil, err
@ -452,7 +446,6 @@ func (b *Builder) BuildPackage(
// builtPaths = append(builtPaths, newBuildPaths...) // builtPaths = append(builtPaths, newBuildPaths...)
slog.Info(gotext.Get("Downloading sources")) slog.Info(gotext.Get("Downloading sources"))
slog.Debug("DownloadSources")
err = b.sourceExecutor.DownloadSources( err = b.sourceExecutor.DownloadSources(
ctx, ctx,
input, input,
@ -466,7 +459,6 @@ func (b *Builder) BuildPackage(
return nil, err return nil, err
} }
slog.Debug("ExecuteSecondPass")
res, err := b.scriptExecutor.ExecuteSecondPass( res, err := b.scriptExecutor.ExecuteSecondPass(
ctx, ctx,
input, input,

@ -20,13 +20,20 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
) )
func NewInstaller(mgr manager.Manager) *Installer { func NewInstaller(
repos PackageFinder,
mgr manager.Manager,
) *Installer {
return &Installer{ return &Installer{
repos: repos,
mgr: mgr, mgr: mgr,
} }
} }
type Installer struct{ mgr manager.Manager } type Installer struct {
repos PackageFinder
mgr manager.Manager
}
func (i *Installer) InstallLocal(paths []string) error { func (i *Installer) InstallLocal(paths []string) error {
return i.mgr.InstallLocal(nil, paths...) return i.mgr.InstallLocal(nil, paths...)

@ -17,18 +17,31 @@
package build package build
import ( import (
"log/slog"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
) )
func NewMainBuilder( func NewMainBuilder(
cfg Config, cfg Config,
mgr manager.Manager,
repos PackageFinder, repos PackageFinder,
scriptExecutor ScriptExecutor, ) *Builder {
installerExecutor InstallerExecutor, s, err := GetSafeScriptExecutor()
) (*Builder, error) { if err != nil {
slog.Info("i will panic")
panic(err)
}
mgr := manager.Detect()
installerExecutor, err := GetSafeInstaller()
if err != nil {
slog.Info("i will panic")
panic(err)
}
builder := &Builder{ builder := &Builder{
scriptExecutor: scriptExecutor, scriptExecutor: s,
cacheExecutor: &Cache{ cacheExecutor: &Cache{
cfg, cfg,
}, },
@ -48,5 +61,5 @@ func NewMainBuilder(
repos: repos, repos: repos,
} }
return builder, nil return builder
} }

@ -18,17 +18,17 @@ package build
import ( import (
"context" "context"
"fmt"
"log/slog" "log/slog"
"net/rpc" "net/rpc"
"os" "os"
"os/exec" "os/exec"
"sync" "syscall"
"github.com/hashicorp/go-plugin" "github.com/hashicorp/go-plugin"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" "gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
) )
var HandshakeConfig = plugin.HandshakeConfig{ var HandshakeConfig = plugin.HandshakeConfig{
@ -217,12 +217,10 @@ var pluginMap = map[string]plugin.Plugin{
"installer": &InstallerPlugin{}, "installer": &InstallerPlugin{},
} }
func GetSafeScriptExecutor() (ScriptExecutor, func(), error) { func GetSafeScriptExecutor() (ScriptExecutor, error) {
var err error
executable, err := os.Executable() executable, err := os.Executable()
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
cmd := exec.Command(executable, "_internal-safe-script-executor") cmd := exec.Command(executable, "_internal-safe-script-executor")
@ -231,21 +229,17 @@ func GetSafeScriptExecutor() (ScriptExecutor, func(), error) {
"LOGNAME=alr", "LOGNAME=alr",
"USER=alr", "USER=alr",
"PATH=/usr/bin:/bin:/usr/local/bin", "PATH=/usr/bin:/bin:/usr/local/bin",
"ALR_LOG_LEVEL=DEBUG",
} }
/*
uid, gid, err := utils.GetUidGidAlrUser() uid, gid, err := utils.GetUidGidAlrUser()
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
cmd.SysProcAttr = &syscall.SysProcAttr{ cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{ Credential: &syscall.Credential{
Uid: uint32(uid), Uid: uint32(uid),
Gid: uint32(gid), Gid: uint32(gid),
}, },
} }
*/
client := plugin.NewClient(&plugin.ClientConfig{ client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: HandshakeConfig, HandshakeConfig: HandshakeConfig,
@ -253,39 +247,17 @@ func GetSafeScriptExecutor() (ScriptExecutor, func(), error) {
Cmd: cmd, Cmd: cmd,
Logger: logger.GetHCLoggerAdapter(), Logger: logger.GetHCLoggerAdapter(),
SkipHostEnv: true, SkipHostEnv: true,
UnixSocketConfig: &plugin.UnixSocketConfig{
Group: "alr",
},
}) })
rpcClient, err := client.Client() rpcClient, err := client.Client()
if err != nil { if err != nil {
return nil, nil, err slog.Info("1")
return nil, err
} }
var cleanupOnce sync.Once raw1, err := rpcClient.Dispense("script-executor")
cleanup := func() {
cleanupOnce.Do(func() {
client.Kill()
})
}
defer func() {
if err != nil { if err != nil {
slog.Debug("close script-executor") return nil, err
cleanup()
}
}()
raw, err := rpcClient.Dispense("script-executor")
if err != nil {
return nil, nil, err
} }
executor, ok := raw.(ScriptExecutor) return raw1.(ScriptExecutor), nil
if !ok {
err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw)
return nil, nil, err
}
return executor, cleanup, nil
} }

@ -17,17 +17,16 @@
package build package build
import ( import (
"fmt"
"log/slog" "log/slog"
"net/rpc" "net/rpc"
"os" "os"
"os/exec" "os/exec"
"sync"
"syscall" "syscall"
"github.com/hashicorp/go-plugin" "github.com/hashicorp/go-plugin"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" "gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
) )
type InstallerPlugin struct { type InstallerPlugin struct {
@ -47,6 +46,7 @@ func (r *InstallerRPC) InstallLocal(paths []string) error {
} }
func (s *InstallerRPCServer) InstallLocal(paths []string, reply *struct{}) error { func (s *InstallerRPCServer) InstallLocal(paths []string, reply *struct{}) error {
slog.Warn("install", "paths", paths)
return s.Impl.InstallLocal(paths) return s.Impl.InstallLocal(paths)
} }
@ -60,9 +60,8 @@ func (s *InstallerRPCServer) Install(pkgs []string, reply *struct{}) error {
} }
func (r *InstallerRPC) RemoveAlreadyInstalled(paths []string) ([]string, error) { func (r *InstallerRPC) RemoveAlreadyInstalled(paths []string) ([]string, error) {
var val []string err := r.client.Call("Plugin.RemoveAlreadyInstalled", paths, nil)
err := r.client.Call("Plugin.RemoveAlreadyInstalled", paths, &val) return nil, err
return val, err
} }
func (s *InstallerRPCServer) RemoveAlreadyInstalled(pkgs []string, res *[]string) error { func (s *InstallerRPCServer) RemoveAlreadyInstalled(pkgs []string, res *[]string) error {
@ -82,38 +81,30 @@ func (p *InstallerPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
return &InstallerRPCServer{Impl: p.Impl}, nil return &InstallerRPCServer{Impl: p.Impl}, nil
} }
func GetSafeInstaller() (InstallerExecutor, func(), error) { func GetSafeInstaller() (InstallerExecutor, error) {
var err error
executable, err := os.Executable() executable, err := os.Executable()
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
cmd := exec.Command(executable, "_internal-installer") cmd := exec.Command(executable, "_internal-installer")
cmd.Env = []string{ cmd.Env = append(os.Environ(),
"HOME=/var/cache/alr", "HOME=/var/cache/alr",
"LOGNAME=alr", "LOGNAME=alr",
"USER=alr", "USER=alr",
"PATH=/usr/bin:/bin:/usr/local/bin", "PATH=/usr/bin:/bin:/usr/local/bin",
"ALR_LOG_LEVEL=DEBUG", "ALR_LOG_LEVEL=DEBUG",
} "XDG_SESSION_CLASS=user",
)
/*
uid, gid, err := utils.GetUidGidAlrUser() uid, gid, err := utils.GetUidGidAlrUser()
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
cmd.SysProcAttr = &syscall.SysProcAttr{ cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{ Credential: &syscall.Credential{
Uid: uint32(uid), Uid: uint32(uid),
Gid: uint32(gid), Gid: uint32(gid),
}, },
} }
*/
slog.Debug("safe installer setup", "uid", syscall.Getuid(), "gid", syscall.Getgid())
client := plugin.NewClient(&plugin.ClientConfig{ client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: HandshakeConfig, HandshakeConfig: HandshakeConfig,
@ -128,33 +119,14 @@ func GetSafeInstaller() (InstallerExecutor, func(), error) {
}) })
rpcClient, err := client.Client() rpcClient, err := client.Client()
if err != nil { if err != nil {
return nil, nil, err slog.Info("1")
return nil, err
} }
var cleanupOnce sync.Once raw1, err := rpcClient.Dispense("installer")
cleanup := func() {
cleanupOnce.Do(func() {
client.Kill()
})
}
defer func() {
if err != nil { if err != nil {
slog.Debug("close installer") return nil, err
cleanup()
}
}()
raw, err := rpcClient.Dispense("installer")
if err != nil {
return nil, nil, err
} }
executor, ok := raw.(InstallerExecutor) return raw1.(InstallerExecutor), nil
if !ok {
err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw)
return nil, nil, err
}
return executor, cleanup, nil
} }

@ -20,7 +20,6 @@ import (
"fmt" "fmt"
"os/exec" "os/exec"
"strings" "strings"
"syscall"
) )
// APTRpm represents the APT-RPM package manager // APTRpm represents the APT-RPM package manager
@ -111,11 +110,7 @@ func (a *APTRpm) UpgradeAll(opts *Opts) error {
func (a *APTRpm) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { func (a *APTRpm) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd var cmd *exec.Cmd
if opts.AsRoot { if opts.AsRoot {
if syscall.Geteuid() != 0 {
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd) cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
} else {
cmd = exec.Command(mgrCmd)
}
cmd.Args = append(cmd.Args, opts.Args...) cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...) cmd.Args = append(cmd.Args, args...)
} else { } else {

@ -115,7 +115,7 @@ func getRootCmd(rootCmd string) string {
func setCmdEnv(cmd *exec.Cmd) { func setCmdEnv(cmd *exec.Cmd) {
cmd.Env = os.Environ() cmd.Env = os.Environ()
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stderr cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
} }

134
repo.go

@ -20,6 +20,7 @@
package main package main
import ( import (
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
@ -27,10 +28,11 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
func AddRepoCmd() *cli.Command { func AddRepoCmd() *cli.Command {
@ -53,32 +55,32 @@ func AddRepoCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfNotRoot(); err != nil { err := utils.ExitIfNotRoot()
if err != nil {
return err return err
} }
ctx := c.Context
name := c.String("name") name := c.String("name")
repoURL := c.String("url") repoURL := c.String("url")
ctx := c.Context cfg := config.New()
err = cfg.Load()
deps, err := appbuilder.
New(ctx).
WithConfig().
Build()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
} }
defer deps.Defer()
cfg := deps.Cfg
reposSlice := cfg.Repos() reposSlice := cfg.Repos()
for _, repo := range reposSlice { for _, repo := range reposSlice {
if repo.URL == repoURL || repo.Name == name { if repo.URL == repoURL {
return cliutils.FormatCliExit(gotext.Get("Repo %s already exists", repo.Name), nil) slog.Error("Repo already exists", "name", repo.Name)
os.Exit(1)
} }
} }
reposSlice = append(reposSlice, types.Repo{ reposSlice = append(reposSlice, types.Repo{
Name: name, Name: name,
URL: repoURL, URL: repoURL,
@ -87,23 +89,27 @@ func AddRepoCmd() *cli.Command {
err = cfg.SaveUserConfig() err = cfg.SaveUserConfig()
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err) slog.Error(gotext.Get("Error saving config"), "err", err)
os.Exit(1)
} }
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { if utils.DropCapsToAlrUser() != nil {
return err slog.Error(gotext.Get("Can't drop privileges"))
os.Exit(1)
} }
deps, err = appbuilder. db := database.New(cfg)
New(ctx). err = db.Init(ctx)
UseConfig(cfg).
WithDB().
WithReposForcePull().
Build()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error pulling repos"), "err", err)
}
rs := repos.New(cfg, db)
err = rs.Pull(ctx, cfg.Repos())
if err != nil {
slog.Error(gotext.Get("Error pulling repos"), "err", err)
os.Exit(1)
} }
defer deps.Defer()
return nil return nil
}, },
@ -124,24 +130,20 @@ func RemoveRepoCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfNotRoot(); err != nil { err := utils.ExitIfNotRoot()
if err != nil {
return err return err
} }
ctx := c.Context ctx := c.Context
name := c.String("name") name := c.String("name")
cfg := config.New()
deps, err := appbuilder. err = cfg.Load()
New(ctx).
WithConfig().
Build()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
} }
defer deps.Defer()
cfg := deps.Cfg
found := false found := false
index := 0 index := 0
@ -153,37 +155,33 @@ func RemoveRepoCmd() *cli.Command {
} }
} }
if !found { if !found {
return cliutils.FormatCliExit(gotext.Get("Repo \"%s\" does not exist", name), nil) slog.Error(gotext.Get("Repo does not exist"), "name", name)
os.Exit(1)
} }
cfg.SetRepos(slices.Delete(reposSlice, index, index+1)) cfg.SetRepos(slices.Delete(reposSlice, index, index+1))
err = os.RemoveAll(filepath.Join(cfg.GetPaths().RepoDir, name)) err = os.RemoveAll(filepath.Join(cfg.GetPaths().RepoDir, name))
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error removing repo directory"), err) slog.Error(gotext.Get("Error removing repo directory"), "err", err)
os.Exit(1)
} }
err = cfg.SaveUserConfig() err = cfg.SaveUserConfig()
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err) slog.Error(gotext.Get("Error saving config"), "err", err)
os.Exit(1)
} }
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { db := database.New(cfg)
return err err = db.Init(ctx)
}
deps, err = appbuilder.
New(ctx).
UseConfig(cfg).
WithDB().
Build()
if err != nil { if err != nil {
return err os.Exit(1)
} }
defer deps.Defer() err = db.DeletePkgs(ctx, "repository = ?", name)
err = deps.DB.DeletePkgs(ctx, "repository = ?", name)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error removing packages from database"), err) slog.Error(gotext.Get("Error removing packages from database"), "err", err)
os.Exit(1)
} }
return nil return nil
@ -197,22 +195,30 @@ func RefreshCmd() *cli.Command {
Usage: gotext.Get("Pull all repositories that have changed"), Usage: gotext.Get("Pull all repositories that have changed"),
Aliases: []string{"ref"}, Aliases: []string{"ref"},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { if utils.DropCapsToAlrUser() != nil {
return err slog.Error(gotext.Get("Can't drop privileges"))
os.Exit(1)
} }
ctx := c.Context ctx := c.Context
cfg := config.New()
deps, err := appbuilder. err := cfg.Load()
New(ctx).
WithConfig().
WithDB().
WithReposForcePull().
Build()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg)
err = db.Init(ctx)
if err != nil {
os.Exit(1)
}
rs := repos.New(cfg, db)
err = rs.Pull(ctx, cfg.Repos())
if err != nil {
slog.Error(gotext.Get("Error pulling repos"), "err", err)
os.Exit(1)
} }
defer deps.Defer()
return nil return nil
}, },
} }

@ -18,13 +18,13 @@ package main
import ( import (
"fmt" "fmt"
"log/slog"
"os" "os"
"text/template" "text/template"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/search"
@ -63,7 +63,7 @@ func SearchCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err return err
} }
@ -93,7 +93,8 @@ func SearchCmd() *cli.Command {
Build(), Build(),
) )
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error while executing search"), err) slog.Error(gotext.Get("Error while executing search"))
return cli.Exit(err, 1)
} }
format := c.String("format") format := c.String("format")
@ -101,7 +102,8 @@ func SearchCmd() *cli.Command {
if format != "" { if format != "" {
tmpl, err = template.New("format").Parse(format) tmpl, err = template.New("format").Parse(format)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error parsing format template"), err) slog.Error(gotext.Get("Error parsing format template"))
return cli.Exit(err, 1)
} }
} }
@ -109,7 +111,8 @@ func SearchCmd() *cli.Command {
if tmpl != nil { if tmpl != nil {
err = tmpl.Execute(os.Stdout, dbPkg) err = tmpl.Execute(os.Stdout, dbPkg)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error executing template"), err) slog.Error(gotext.Get("Error executing template"))
return cli.Exit(err, 1)
} }
fmt.Println() fmt.Println()
} else { } else {

@ -23,14 +23,14 @@ import (
"context" "context"
"fmt" "fmt"
"log/slog" "log/slog"
"os"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go.elara.ws/vercmp" "go.elara.ws/vercmp"
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" "gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
@ -38,6 +38,7 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/search"
) )
@ -54,59 +55,61 @@ func UpgradeCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfNotRoot(); err != nil { err := utils.DropCapsToAlrUser()
return err
}
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
installer, installerClose, err := build.GetSafeInstaller()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error dropping capabilities"), "err", err)
os.Exit(1)
} }
defer installerClose()
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
return err
}
scripter, scripterClose, err := build.GetSafeScriptExecutor()
if err != nil {
return err
}
defer scripterClose()
ctx := c.Context ctx := c.Context
deps, err := appbuilder. cfg := config.New()
New(ctx). err = cfg.Load()
WithConfig().
WithDB().
WithRepos().
WithDistroInfo().
WithManager().
Build()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
} }
defer deps.Defer()
builder, err := build.NewMainBuilder( db := database.New(cfg)
deps.Cfg, rs := repos.New(cfg, db)
deps.Manager, err = db.Init(ctx)
deps.Repos, if err != nil {
scripter, slog.Error(gotext.Get("Error initialization database"), "err", err)
installer, os.Exit(1)
}
slog.Debug("builder setup")
builder := build.NewMainBuilder(
cfg,
rs,
) )
info, err := distro.ParseOSRelease(ctx)
slog.Debug("ParseOSRelease", "err", err)
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error parsing os-release file"), "err", err)
os.Exit(1)
} }
updates, err := checkForUpdates(ctx, deps.Manager, deps.DB, deps.Info) mgr := manager.Detect()
if mgr == nil {
slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
}
if cfg.AutoPull() {
slog.Debug("autopull")
err = rs.Pull(ctx, cfg.Repos())
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error checking for updates"), err) slog.Error(gotext.Get("Error pulling repos"), "err", err)
os.Exit(1)
}
}
updates, err := checkForUpdates(ctx, mgr, cfg, db, rs, info)
if err != nil {
slog.Error(gotext.Get("Error checking for updates"), "err", err)
os.Exit(1)
} }
if len(updates) > 0 { if len(updates) > 0 {
@ -117,13 +120,14 @@ func UpgradeCmd() *cli.Command {
Clean: c.Bool("clean"), Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"), Interactive: c.Bool("interactive"),
}, },
Info: deps.Info, Info: info,
PkgFormat_: build.GetPkgFormat(deps.Manager), PkgFormat_: build.GetPkgFormat(mgr),
}, },
updates, updates,
) )
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error checking for updates"), err) slog.Error(gotext.Get("Error checking for updates"), "err", err)
os.Exit(1)
} }
} else { } else {
slog.Info(gotext.Get("There is nothing to do.")) slog.Info(gotext.Get("There is nothing to do."))
@ -137,7 +141,9 @@ func UpgradeCmd() *cli.Command {
func checkForUpdates( func checkForUpdates(
ctx context.Context, ctx context.Context,
mgr manager.Manager, mgr manager.Manager,
cfg *config.ALRConfig,
db *database.Database, db *database.Database,
rs *repos.Repos,
info *distro.OSRelease, info *distro.OSRelease,
) ([]database.Package, error) { ) ([]database.Package, error) {
installed, err := mgr.ListInstalled(nil) installed, err := mgr.ListInstalled(nil)