Compare commits

...

10 Commits

Author SHA1 Message Date
57225e05bd wip 2025-04-15 17:52:50 +03:00
6df5baa457 wip 2025-04-15 10:34:07 +03:00
35982a0ae7 wip 2025-04-15 01:40:50 +03:00
240ee852c8 wip 2025-04-15 01:32:02 +03:00
fe3acf5b85 wip 2025-04-15 00:20:57 +03:00
613dc41fdf wip 2025-04-15 00:04:55 +03:00
6d5016270f wip 2025-04-14 23:35:10 +03:00
e3aaa88822 refactor 2025-04-13 20:22:32 +03:00
b8cb7af3bb refactor 2025-04-13 19:54:04 +03:00
f26b72b2a2 refactor 2025-04-13 19:39:01 +03:00
31 changed files with 1154 additions and 904 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.7%</text> <text x="86" y="15" fill="#010101" fill-opacity=".3">15.8%</text>
<text x="86" y="14">15.7%</text> <text x="86" y="14">15.8%</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">98.00%</text> <text x="100" y="15" fill="#010101" fill-opacity=".3">97.00%</text>
<text x="100" y="14">98.00%</text> <text x="100" y="14">97.00%</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 940 B

After

Width:  |  Height:  |  Size: 940 B

230
build.go

@ -20,25 +20,20 @@
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/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"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 {
@ -69,115 +64,66 @@ 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 {
slog.Error(gotext.Get("Error getting working directory"), "err", err) return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err)
os.Exit(1)
}
executable, err := os.Executable()
if err != nil {
slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
} }
cmd := exec.Command(executable, "_internal-mount", wd) wd, wdCleanup, err := Mount(wd)
var stdout bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error getting working directory"), "err", err) return 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()
err = cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg) deps, err := appbuilder.
rs := repos.New(cfg, db) New(ctx).
err = db.Init(ctx) WithConfig().
WithDB().
WithReposNoPull().
WithDistroInfo().
WithManager().
Build()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err) return cli.Exit(err, 1)
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
switch { var scriptArgs *build.BuildPackageFromScriptArgs
case c.IsSet("script"): var dbArgs *build.BuildPackageFromDbArgs
script = c.String("script")
packages = append(packages, c.String("script-package"))
res, err = builder.BuildPackageFromScript( buildArgs := &build.BuildArgs{
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(mgr), PkgFormat_: build.GetPkgFormat(deps.Manager),
Info: info, Info: deps.Info,
}, }
},
) switch {
case c.IsSet("script"):
script, err = filepath.Abs(c.String("script"))
if err != nil { if err != nil {
slog.Error(gotext.Get("Error building package"), "err", err) return cliutils.FormatCliExit(gotext.Get("Cannot get absolute script path"), 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
@ -191,51 +137,97 @@ func BuildCmd() *cli.Command {
packageSearch = arr[0] packageSearch = arr[0]
} }
pkgs, _, _ := rs.FindPkgs(ctx, []string{packageSearch}) pkgs, _, err := deps.Repos.FindPkgs(ctx, []string{packageSearch})
pkg, ok := pkgs[packageSearch] if err != nil {
if len(pkg) < 1 || !ok { return cliutils.FormatCliExit("failed to find pkgs", err)
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)
} }
res, err = builder.BuildPackageFromDb( dbArgs = &build.BuildPackageFromDbArgs{
ctx,
&build.BuildPackageFromDbArgs{
Package: &pkg[0], Package: &pkg[0],
Packages: packages, Packages: packages,
BuildArgs: build.BuildArgs{ BuildArgs: *buildArgs,
Opts: &types.BuildOpts{
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
},
PkgFormat_: build.GetPkgFormat(mgr),
Info: info,
},
},
)
if err != nil {
slog.Error(gotext.Get("Error building package"), "err", err)
os.Exit(1)
} }
default: default:
slog.Error(gotext.Get("Nothing to build")) return cliutils.FormatCliExit(gotext.Get("Nothing to build"), nil)
os.Exit(1) }
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(
ctx,
dbArgs,
)
}
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error building package"), err)
} }
// Перемещение собранных пакетов в рабочую директорию
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 {
slog.Error(gotext.Get("Error moving the package"), "err", err) return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err)
os.Exit(1)
} }
} }
slog.Info(gotext.Get("Done"))
return nil return nil
}, },
} }

17
fix.go

@ -27,6 +27,7 @@ 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"
) )
@ -36,7 +37,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.ExitIfCantDropCapsToAlrUser(); err != nil { if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
return err return err
} }
@ -60,22 +61,19 @@ func FixCmd() *cli.Command {
dir, err := os.Open(paths.CacheDir) dir, err := os.Open(paths.CacheDir)
if err != nil { if err != nil {
slog.Error(gotext.Get("Unable to open cache directory")) return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err)
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 {
slog.Error(gotext.Get("Unable to read cache directory contents")) return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err)
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 {
slog.Error(gotext.Get("Unable to remove cache item"), "item", entry) return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), err)
return cli.Exit(err, 1)
} }
} }
@ -83,15 +81,14 @@ func FixCmd() *cli.Command {
err = os.MkdirAll(paths.CacheDir, 0o755) err = os.MkdirAll(paths.CacheDir, 0o755)
if err != nil { if err != nil {
slog.Error(gotext.Get("Unable to create new cache directory")) return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err)
return cli.Exit(err, 1)
} }
deps, err = appbuilder. deps, err = appbuilder.
New(ctx). New(ctx).
WithConfig(). WithConfig().
WithDB(). WithDB().
WithRepos(). WithReposForcePull().
Build() Build()
if err != nil { if err != nil {
return cli.Exit(err, 1) return cli.Exit(err, 1)

4
go.mod

@ -4,8 +4,6 @@ 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
@ -16,7 +14,6 @@ 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
@ -68,6 +65,7 @@ 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,8 +106,9 @@ 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.19 h1:tUN6H7LWqNx4hQVxomd0CVsDwaDr9gaRQaI4GpSmrsA= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.19/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
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,6 +30,7 @@ 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"
@ -71,19 +72,17 @@ 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())
os.Exit(1) return cli.Exit(gotext.Get("No such helper command"), 1)
} }
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error getting working directory"), "err", err) return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err)
os.Exit(1)
} }
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error getting working directory"), "err", err) return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err)
os.Exit(1)
} }
hc := interp.HandlerContext{ hc := interp.HandlerContext{

59
info.go

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

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

@ -17,13 +17,14 @@
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"
@ -31,13 +32,14 @@ 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"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
"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 {
@ -47,16 +49,17 @@ 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()
if err != nil { slog.Debug("start _internal-safe-script-executor", "uid", syscall.Getuid(), "gid", syscall.Getgid())
slog.Error("aa", "err", err)
os.Exit(1) if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
} }
cfg := config.New() cfg := config.New()
err = cfg.Load() err := cfg.Load()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err) return cliutils.FormatCliExit(gotext.Get("Error loading config"), err)
os.Exit(1)
} }
logger := hclog.New(&hclog.LoggerOptions{ logger := hclog.New(&hclog.LoggerOptions{
@ -66,6 +69,7 @@ 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{
@ -87,26 +91,28 @@ 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 != nil { if err := utils.EnsureIsAlrUser(); err != nil {
slog.Error("err") return err
os.Exit(1)
} }
cfg := config.New() // Before escalating the rights, we made sure that
err = cfg.Load() // this is an ALR user, so it looks safe.
err := utils.EscalateToRootUid()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err) return cliutils.FormatCliExit("cannot escalate to root", err)
os.Exit(1)
} }
db := database.New(cfg) deps, err := appbuilder.
rs := repos.New(cfg, db) New(c.Context).
err = db.Init(c.Context) WithConfig().
WithDB().
WithReposNoPull().
Build()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err) return err
os.Exit(1)
} }
defer deps.Defer()
logger := hclog.New(&hclog.LoggerOptions{ logger := hclog.New(&hclog.LoggerOptions{
Name: "plugin", Name: "plugin",
@ -121,7 +127,6 @@ 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(),
), ),
}, },
@ -133,52 +138,111 @@ 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-mount", Name: "_internal-temporary-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, _ := user.Current() u, err := user.Current()
logger.SetupForGoPlugin()
err := syscall.Setuid(0)
if err != nil { if err != nil {
slog.Error("Failed to setuid(0)", "err", err) return cliutils.FormatCliExit("cannot get current user", err)
os.Exit(1)
} }
alrRunDir := "/var/run/alr" _, alrGid, err := utils.GetUidGidAlrUser()
err = os.MkdirAll(alrRunDir, 0o750)
if err != nil { if err != nil {
slog.Error("Error creating /var/run/alr directory", "err", err) return cliutils.FormatCliExit("cannot get alr user", err)
os.Exit(1)
} }
_, gid, _ := utils.GetUidGidAlrUser() if _, err := os.Stat(sourceDir); err != nil {
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)
} }
// Создаем поддиректорию для bindfs if err := utils.EnuseIsPrivilegedGroupMember(); err != nil {
targetDir := filepath.Join(alrRunDir, fmt.Sprintf("bindfs-%d", os.Getpid())) return err
err = os.MkdirAll(targetDir, 0o750) // 0750: владелец (root) и группа (alr) имеют доступ
if err != nil {
slog.Error("Error creating bindfs target directory", "err", err)
os.Exit(1)
} }
// Устанавливаем владельца и группу (root:alr) // Before escalating the rights, we made sure that
err = os.Chown(targetDir, 0, gid) // 1. user in wheel group
if err != nil { // 2. user can access sourceDir
slog.Error("Failed to chown bindfs directory", "err", err) if err := utils.EscalateToRootUid(); err != nil {
os.Exit(1) return err
}
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(
@ -190,74 +254,24 @@ func InternalMountCmd() *cli.Command {
bindfsCmd.Stderr = os.Stderr bindfsCmd.Stderr = os.Stderr
if err := bindfsCmd.Start(); err != nil { if err := bindfsCmd.Run(); err != nil {
slog.Error("Error starting bindfs", "err", err) return cliutils.FormatCliExit("failed to strart bindfs", err)
os.Exit(1)
} }
fmt.Print(targetDir) fmt.Println(targetDir)
return nil _, _ = bufio.NewReader(os.Stdin).ReadString('\n')
},
}
}
func InternalUnmountCmd() *cli.Command { slog.Debug("start unmount", "dir", targetDir)
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 {
slog.Error("Error unmounting directory", "dir", targetDir, "err", err) return cliutils.FormatCliExit(fmt.Sprintf("failed to unmount %s", targetDir), err)
os.Exit(1)
} }
if err := os.Remove(targetDir); err != nil { if err := os.Remove(targetDir); err != nil {
slog.Error("Error removing directory", "dir", targetDir, "err", err) return cliutils.FormatCliExit(fmt.Sprintf("error removing directory %s", targetDir), err)
os.Exit(1)
} }
return nil return nil

@ -22,10 +22,12 @@ 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"
) )
@ -33,6 +35,8 @@ 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() {
@ -53,6 +57,14 @@ 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
@ -60,8 +72,7 @@ func (b *AppBuilder) WithConfig() *AppBuilder {
cfg := config.New() cfg := config.New()
if err := cfg.Load(); err != nil { if err := cfg.Load(); err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err) b.err = cliutils.FormatCliExit(gotext.Get("Error loading config"), err)
b.err = cli.Exit("", 1)
return b return b
} }
@ -82,8 +93,7 @@ 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 {
slog.Error(gotext.Get("Error initialization database"), "err", err) b.err = cliutils.FormatCliExit(gotext.Get("Error initialization database"), err)
b.err = cli.Exit("", 1)
return b return b
} }
@ -92,6 +102,21 @@ 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
} }
@ -105,10 +130,9 @@ func (b *AppBuilder) WithRepos() *AppBuilder {
rs := repos.New(cfg, db) rs := repos.New(cfg, db)
if cfg.AutoPull() { if enablePull && (forcePull || cfg.AutoPull()) {
if err := rs.Pull(b.ctx, cfg.Repos()); err != nil { if err := rs.Pull(b.ctx, cfg.Repos()); err != nil {
slog.Error(gotext.Get("Error pulling repositories"), "err", err) b.err = cliutils.FormatCliExit(gotext.Get("Error pulling repositories"), err)
b.err = cli.Exit("", 1)
return b return b
} }
} }
@ -118,6 +142,32 @@ func (b *AppBuilder) WithRepos() *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

@ -0,0 +1,60 @@
// 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,6 +28,7 @@ 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"
) )
@ -83,14 +84,9 @@ 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(
systemConfigPath, constants.SystemConfigPath,
) )
if err != nil { if err != nil {
slog.Debug("Cannot read system config", "err", err) slog.Debug("Cannot read system config", "err", err)
@ -108,8 +104,8 @@ func (c *ALRConfig) Load() error {
c.cfg = config c.cfg = config
c.paths = &Paths{} c.paths = &Paths{}
c.paths.UserConfigPath = systemConfigPath c.paths.UserConfigPath = constants.SystemConfigPath
c.paths.CacheDir = systemCachePath c.paths.CacheDir = constants.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")

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

@ -16,80 +16,66 @@ 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:47 #: build.go:42
msgid "Build a local package" msgid "Build a local package"
msgstr "Сборка локального пакета" msgstr "Сборка локального пакета"
#: build.go:53 #: build.go:48
msgid "Path to the build script" msgid "Path to the build script"
msgstr "Путь к скрипту сборки" msgstr "Путь к скрипту сборки"
#: build.go:58 #: build.go:53
msgid "Specify subpackage in script (for multi package script only)" msgid "Specify subpackage in script (for multi package script only)"
msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)" msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)"
#: build.go:63 #: build.go:58
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:68 #: build.go:63
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:74 build.go:79 build.go:89 build.go:103 #: build.go:73
msgid "Error getting working directory" msgid "Error getting working directory"
msgstr "Ошибка при получении рабочего каталога" msgstr "Ошибка при получении рабочего каталога"
#: build.go:110 build.go:115 #: build.go:118
#, fuzzy msgid "Cannot get absolute script path"
msgid "Error dropping capabilities" msgstr ""
msgstr "Ошибка при открытии базы данных"
#: build.go:123 #: build.go:148
#, 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:225 #: build.go:161
#, fuzzy #, fuzzy
msgid "Nothing to build" msgid "Nothing to build"
msgstr "Исполнение build()" msgstr "Исполнение build()"
#: build.go:234 #: build.go:218
msgid "Error building package"
msgstr "Ошибка при сборке пакета"
#: build.go:225
msgid "Error moving the package" msgid "Error moving the package"
msgstr "Ошибка при перемещении пакета" msgstr "Ошибка при перемещении пакета"
#: fix.go:37 #: build.go:229
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:58 #: fix.go:59
#, fuzzy #, fuzzy
msgid "Clearing cache directory" msgid "Clearing cache directory"
msgstr "Удаление каталога кэша" msgstr "Удаление каталога кэша"
#: fix.go:63 #: fix.go:64
#, fuzzy #, fuzzy
msgid "Unable to open cache directory" msgid "Unable to open cache directory"
msgstr "Не удалось удалить каталог кэша" msgstr "Не удалось удалить каталог кэша"
@ -99,23 +85,19 @@ msgstr "Не удалось удалить каталог кэша"
msgid "Unable to read cache directory contents" msgid "Unable to read cache directory contents"
msgstr "Не удалось удалить каталог кэша" msgstr "Не удалось удалить каталог кэша"
#: fix.go:77 #: fix.go:76
#, fuzzy #, fuzzy
msgid "Unable to remove cache item" msgid "Unable to remove cache item (%s)"
msgstr "Не удалось удалить каталог кэша" msgstr "Не удалось удалить каталог кэша"
#: fix.go:82 #: fix.go:80
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "Восстановление кэша" msgstr "Восстановление кэша"
#: fix.go:86 #: fix.go:84
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 из шаблона"
@ -124,91 +106,108 @@ 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:41 #: helper.go:42
msgid "List all the available helper commands" msgid "List all the available helper commands"
msgstr "Список всех доступных вспомогательных команды" msgstr "Список всех доступных вспомогательных команды"
#: helper.go:53 #: helper.go:54
msgid "Run a ALR helper command" msgid "Run a ALR helper command"
msgstr "Запустить вспомогательную команду ALR" msgstr "Запустить вспомогательную команду ALR"
#: helper.go:60 #: helper.go:61
msgid "The directory that the install commands will install to" msgid "The directory that the install commands will install to"
msgstr "Каталог, в который будут устанавливать команды установки" msgstr "Каталог, в который будут устанавливать команды установки"
#: helper.go:73 #: helper.go:74 helper.go:75
msgid "No such helper command" msgid "No such helper command"
msgstr "Такой вспомогательной команды нет" msgstr "Такой вспомогательной команды нет"
#: info.go:44 #: helper.go:85
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:49 #: info.go:47
msgid "Show all information, not just for the current distro" msgid "Show all information, not just for the current distro"
msgstr "Показывать всю информацию, не только для текущего дистрибутива" msgstr "Показывать всю информацию, не только для текущего дистрибутива"
#: info.go:75 #: info.go:68
msgid "Error getting packages" msgid "Error getting packages"
msgstr "Ошибка при получении пакетов" msgstr "Ошибка при получении пакетов"
#: info.go:84 #: info.go:76
msgid "Error iterating over packages" msgid "Error iterating over packages"
msgstr "Ошибка при переборе пакетов" msgstr "Ошибка при переборе пакетов"
#: info.go:98 #: info.go:90
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:118 #: info.go:110
msgid "Error finding packages" msgid "Error finding packages"
msgstr "Ошибка при поиске пакетов" msgstr "Ошибка при поиске пакетов"
#: info.go:134 #: info.go:124
#, fuzzy #, fuzzy
msgid "Can't detect system language" msgid "Can't detect system language"
msgstr "Ошибка при парсинге языка системы" msgstr "Ошибка при парсинге языка системы"
#: info.go:144 #: info.go:141
msgid "Error parsing os-release file"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: info.go:153
msgid "Error resolving overrides" msgid "Error resolving overrides"
msgstr "Ошибка устранения переорпеделений" msgstr "Ошибка устранения переорпеделений"
#: info.go:162 info.go:168 #: info.go:149 info.go:154
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "Ошибка кодирования переменных скрита" msgstr "Ошибка кодирования переменных скрита"
#: install.go:43 #: install.go:40
msgid "Install a new package" msgid "Install a new package"
msgstr "Установить новый пакет" msgstr "Установить новый пакет"
#: install.go:57 #: install.go:56
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:96 #: install.go:118
msgid "Error pulling repositories" msgid "Error parsing os release"
msgstr "Ошибка при извлечении репозиториев" msgstr "Ошибка при разборе файла выпуска операционной системы"
#: install.go:164 #: install.go:163
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "Удалить установленный пакет" msgstr "Удалить установленный пакет"
#: install.go:189 #: install.go:182
msgid "Error listing installed packages" msgid "Error listing installed packages"
msgstr "Ошибка при составлении списка установленных пакетов" msgstr "Ошибка при составлении списка установленных пакетов"
#: install.go:227 #: install.go:223
msgid "Command remove expected at least 1 argument, got %d" msgid "Command remove expected at least 1 argument, got %d"
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d" msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d"
#: install.go:242 #: install.go:239
msgid "Error removing packages" msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов" 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"
msgstr "Ошибка при извлечении репозиториев"
#: internal/cliutils/app_builder/builder.go:165
msgid "Unable to detect a supported package manager on the system"
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"
msgstr "Показать скрипт для пакета %s" msgstr "Показать скрипт для пакета %s"
@ -327,10 +326,19 @@ msgstr "%s %s загружается — %s/с\n"
msgid "ERROR" msgid "ERROR"
msgstr "ОШИБКА" msgstr "ОШИБКА"
#: internal/utils/cmd.go:94 #: internal/utils/cmd.go:95
#, 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"
@ -339,35 +347,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:79 #: main.go:61
msgid "Arguments to be passed on to the package manager" msgid "Arguments to be passed on to the package manager"
msgstr "Аргументы, которые будут переданы менеджеру пакетов" msgstr "Аргументы, которые будут переданы менеджеру пакетов"
#: main.go:85 #: main.go:67
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "Включение интерактивных вопросов и запросов" msgstr "Включение интерактивных вопросов и запросов"
#: main.go:185 #: main.go:147
msgid "Show help" msgid "Show help"
msgstr "Показать справку" msgstr "Показать справку"
#: main.go:189 #: main.go:151
msgid "Error while running app" msgid "Error while running app"
msgstr "Ошибка при запуске приложения" msgstr "Ошибка при запуске приложения"
#: pkg/build/build.go:392 #: pkg/build/build.go:394
msgid "Building package" msgid "Building package"
msgstr "Сборка пакета" msgstr "Сборка пакета"
#: pkg/build/build.go:421 #: pkg/build/build.go:423
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:448 #: pkg/build/build.go:454
msgid "Downloading sources" msgid "Downloading sources"
msgstr "Скачивание источников" msgstr "Скачивание источников"
#: pkg/build/build.go:535 #: pkg/build/build.go:543
msgid "Installing dependencies" msgid "Installing dependencies"
msgstr "Установка зависимостей" msgstr "Установка зависимостей"
@ -441,52 +449,50 @@ msgstr ""
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте " "Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
"обновить ALR, если что-то не работает." "обновить ALR, если что-то не работает."
#: repo.go:41 #: repo.go:39
msgid "Add a new repository" msgid "Add a new repository"
msgstr "Добавить новый репозиторий" msgstr "Добавить новый репозиторий"
#: repo.go:48 #: repo.go:46
msgid "Name of the new repo" msgid "Name of the new repo"
msgstr "Название нового репозитория" msgstr "Название нового репозитория"
#: repo.go:54 #: repo.go:52
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "URL-адрес нового репозитория" msgstr "URL-адрес нового репозитория"
#: repo.go:92 repo.go:172 #: repo.go:79
#, 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:97 repo.go:199 #: repo.go:116
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:129 #: repo.go:123
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "Название репозитория удалён" msgstr "Название репозитория удалён"
#: repo.go:158 #: repo.go:156
msgid "Repo does not exist" #, fuzzy
msgid "Repo \"%s\" does not exist"
msgstr "Репозитория не существует" msgstr "Репозитория не существует"
#: repo.go:166 #: repo.go:163
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "Ошибка при удалении каталога репозитория" msgstr "Ошибка при удалении каталога репозитория"
#: repo.go:183 #: repo.go:186
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "Ошибка при удалении пакетов из базы данных" msgstr "Ошибка при удалении пакетов из базы данных"
#: repo.go:195 #: repo.go:197
msgid "Pull all repositories that have changed" msgid "Pull all repositories that have changed"
msgstr "Скачать все изменённые репозитории" msgstr "Скачать все изменённые репозитории"
@ -519,26 +525,37 @@ msgstr "Формат выходных данных с использование
msgid "Error while executing search" msgid "Error while executing search"
msgstr "Ошибка при запуске приложения" msgstr "Ошибка при запуске приложения"
#: search.go:105 #: search.go:104
msgid "Error parsing format template" msgid "Error parsing format template"
msgstr "Ошибка при разборе шаблона" msgstr "Ошибка при разборе шаблона"
#: search.go:114 #: search.go:112
msgid "Error executing template" msgid "Error executing template"
msgstr "Ошибка при выполнении шаблона" msgstr "Ошибка при выполнении шаблона"
#: upgrade.go:48 #: upgrade.go:47
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "Обновить все установленные пакеты" msgstr "Обновить все установленные пакеты"
#: upgrade.go:111 upgrade.go:129 #: upgrade.go:109 upgrade.go:126
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "Ошибка при проверке обновлений" msgstr "Ошибка при проверке обновлений"
#: upgrade.go:133 #: upgrade.go:129
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,7 +18,6 @@ package utils
import ( import (
"errors" "errors"
"log/slog"
"os" "os"
"os/user" "os/user"
"strconv" "strconv"
@ -26,6 +25,9 @@ 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) {
@ -68,6 +70,66 @@ 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")
@ -79,19 +141,46 @@ func DropCapsToAlrUser() error {
return nil return nil
} }
// Returns cli.Exit to func EnuseIsPrivilegedGroupMember() error {
func ExitIfCantDropCapsToAlrUser() cli.ExitCoder { currentUser, err := user.Current()
err := DropCapsToAlrUser()
if err != nil { if err != nil {
slog.Debug("dropping capabilities error", "err", err) return err
return cli.Exit(gotext.Get("Error dropping capabilities"), 1)
} }
group, err := user.LookupGroup(constants.PrivilegedGroup)
if err != nil {
return err
}
groups, err := currentUser.GroupIds()
if err != nil {
return err
}
for _, gid := range groups {
if gid == group.Gid {
return nil return nil
}
}
return cliutils.FormatCliExit(gotext.Get("You need to be a %s member to perform this action", constants.PrivilegedGroup), nil)
} }
func ExitIfNotRoot() error { func EscalateToRootGid() error {
if os.Getuid() != 0 { return syscall.Setgid(0)
return cli.Exit(gotext.Get("You need to be root to perform this action"), 1) }
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
} }

23
internal/utils/utils.go Normal file

@ -0,0 +1,23 @@
// 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.ExitIfCantDropCapsToAlrUser(); err != nil { if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
return err return err
} }
@ -77,8 +77,7 @@ func ListCmd() *cli.Command {
result, err := db.GetPkgs(ctx, where, args...) result, err := db.GetPkgs(ctx, where, args...)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error getting packages"), "err", err) return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
os.Exit(1)
} }
defer result.Close() defer result.Close()
@ -86,14 +85,13 @@ func ListCmd() *cli.Command {
if c.Bool("installed") { if c.Bool("installed") {
mgr := manager.Detect() mgr := manager.Detect()
if mgr == nil { if mgr == nil {
slog.Error(gotext.Get("Unable to detect a supported package manager on the system")) return cli.Exit(gotext.Get("Unable to detect a supported package manager on the system"), 1)
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)
os.Exit(1) return cli.Exit(err, 1)
} }
for pkgName, version := range installed { for pkgName, version := range installed {
@ -110,7 +108,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 err return cli.Exit(err, 1)
} }
if slices.Contains(cfg.IgnorePkgUpdates(), pkg.Name) { if slices.Contains(cfg.IgnorePkgUpdates(), pkg.Name) {
@ -130,11 +128,6 @@ 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,24 +50,6 @@ 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",
@ -100,41 +82,21 @@ func GetApp() *cli.App {
HelperCmd(), HelperCmd(),
VersionCmd(), VersionCmd(),
SearchCmd(), SearchCmd(),
// TEST // Internal commands
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(c *cli.Context, err error) { ExitErrHandler: func(cCtx *cli.Context, err error) {
cliutils.HandleExitCoder(err)
}, },
} }
} }

@ -352,16 +352,17 @@ 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)
@ -384,6 +385,7 @@ 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
@ -423,21 +425,25 @@ 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
@ -446,6 +452,7 @@ 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,
@ -459,6 +466,7 @@ 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,20 +20,13 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
) )
func NewInstaller( func NewInstaller(mgr manager.Manager) *Installer {
repos PackageFinder,
mgr manager.Manager,
) *Installer {
return &Installer{ return &Installer{
repos: repos,
mgr: mgr, mgr: mgr,
} }
} }
type Installer struct { type Installer struct{ mgr manager.Manager }
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,31 +17,18 @@
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,
) *Builder { scriptExecutor ScriptExecutor,
s, err := GetSafeScriptExecutor() installerExecutor InstallerExecutor,
if err != nil { ) (*Builder, error) {
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: s, scriptExecutor: scriptExecutor,
cacheExecutor: &Cache{ cacheExecutor: &Cache{
cfg, cfg,
}, },
@ -61,5 +48,5 @@ func NewMainBuilder(
repos: repos, repos: repos,
} }
return builder return builder, nil
} }

@ -17,16 +17,17 @@
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 {
@ -46,7 +47,6 @@ 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,8 +60,9 @@ 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) {
err := r.client.Call("Plugin.RemoveAlreadyInstalled", paths, nil) var val []string
return nil, err err := r.client.Call("Plugin.RemoveAlreadyInstalled", paths, &val)
return val, err
} }
func (s *InstallerRPCServer) RemoveAlreadyInstalled(pkgs []string, res *[]string) error { func (s *InstallerRPCServer) RemoveAlreadyInstalled(pkgs []string, res *[]string) error {
@ -81,30 +82,38 @@ func (p *InstallerPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
return &InstallerRPCServer{Impl: p.Impl}, nil return &InstallerRPCServer{Impl: p.Impl}, nil
} }
func GetSafeInstaller() (InstallerExecutor, error) { func GetSafeInstaller() (InstallerExecutor, func(), error) {
var err error
executable, err := os.Executable() executable, err := os.Executable()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
cmd := exec.Command(executable, "_internal-installer") cmd := exec.Command(executable, "_internal-installer")
cmd.Env = append(os.Environ(), cmd.Env = []string{
"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, err return nil, 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,
@ -119,14 +128,33 @@ func GetSafeInstaller() (InstallerExecutor, error) {
}) })
rpcClient, err := client.Client() rpcClient, err := client.Client()
if err != nil { if err != nil {
slog.Info("1") return nil, nil, err
return nil, err
} }
raw1, err := rpcClient.Dispense("installer") var cleanupOnce sync.Once
cleanup := func() {
cleanupOnce.Do(func() {
client.Kill()
})
}
defer func() {
if err != nil { if err != nil {
return nil, err slog.Debug("close installer")
cleanup()
}
}()
raw, err := rpcClient.Dispense("installer")
if err != nil {
return nil, nil, err
} }
return raw1.(InstallerExecutor), nil executor, ok := raw.(InstallerExecutor)
if !ok {
err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw)
return nil, nil, err
}
return executor, cleanup, nil
} }

@ -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"
"syscall" "sync"
"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,10 +217,12 @@ var pluginMap = map[string]plugin.Plugin{
"installer": &InstallerPlugin{}, "installer": &InstallerPlugin{},
} }
func GetSafeScriptExecutor() (ScriptExecutor, error) { func GetSafeScriptExecutor() (ScriptExecutor, func(), error) {
var err error
executable, err := os.Executable() executable, err := os.Executable()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
cmd := exec.Command(executable, "_internal-safe-script-executor") cmd := exec.Command(executable, "_internal-safe-script-executor")
@ -229,17 +231,21 @@ func GetSafeScriptExecutor() (ScriptExecutor, 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, err return nil, 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,
@ -247,17 +253,39 @@ func GetSafeScriptExecutor() (ScriptExecutor, 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 {
slog.Info("1") return nil, nil, err
return nil, err
} }
raw1, err := rpcClient.Dispense("script-executor") var cleanupOnce sync.Once
cleanup := func() {
cleanupOnce.Do(func() {
client.Kill()
})
}
defer func() {
if err != nil { if err != nil {
return nil, err slog.Debug("close script-executor")
cleanup()
}
}()
raw, err := rpcClient.Dispense("script-executor")
if err != nil {
return nil, nil, err
} }
return raw1.(ScriptExecutor), nil executor, ok := raw.(ScriptExecutor)
if !ok {
err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw)
return nil, nil, err
}
return executor, cleanup, nil
} }

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"os/exec" "os/exec"
"strings" "strings"
"syscall"
) )
// APTRpm represents the APT-RPM package manager // APTRpm represents the APT-RPM package manager
@ -110,7 +111,11 @@ 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.Stdout cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
} }

134
repo.go

@ -20,7 +20,6 @@
package main package main
import ( import (
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
@ -28,11 +27,10 @@ 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/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"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 {
@ -55,32 +53,32 @@ func AddRepoCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
err := utils.ExitIfNotRoot() if err := utils.ExitIfNotRoot(); err != nil {
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")
cfg := config.New() ctx := c.Context
err = cfg.Load()
deps, err := appbuilder.
New(ctx).
WithConfig().
Build()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err) return 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 { if repo.URL == repoURL || repo.Name == name {
slog.Error("Repo already exists", "name", repo.Name) return cliutils.FormatCliExit(gotext.Get("Repo %s already exists", repo.Name), nil)
os.Exit(1)
} }
} }
reposSlice = append(reposSlice, types.Repo{ reposSlice = append(reposSlice, types.Repo{
Name: name, Name: name,
URL: repoURL, URL: repoURL,
@ -89,27 +87,23 @@ func AddRepoCmd() *cli.Command {
err = cfg.SaveUserConfig() err = cfg.SaveUserConfig()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error saving config"), "err", err) return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
os.Exit(1)
} }
if utils.DropCapsToAlrUser() != nil { if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
slog.Error(gotext.Get("Can't drop privileges")) return err
os.Exit(1)
} }
db := database.New(cfg) deps, err = appbuilder.
err = db.Init(ctx) New(ctx).
UseConfig(cfg).
WithDB().
WithReposForcePull().
Build()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error pulling repos"), "err", err) return 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
}, },
@ -130,20 +124,24 @@ func RemoveRepoCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
err := utils.ExitIfNotRoot() if err := utils.ExitIfNotRoot(); err != nil {
if err != nil {
return err return err
} }
ctx := c.Context ctx := c.Context
name := c.String("name") name := c.String("name")
cfg := config.New()
err = cfg.Load() deps, err := appbuilder.
New(ctx).
WithConfig().
Build()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err) return err
os.Exit(1)
} }
defer deps.Defer()
cfg := deps.Cfg
found := false found := false
index := 0 index := 0
@ -155,33 +153,37 @@ func RemoveRepoCmd() *cli.Command {
} }
} }
if !found { if !found {
slog.Error(gotext.Get("Repo does not exist"), "name", name) return cliutils.FormatCliExit(gotext.Get("Repo \"%s\" does not exist", name), nil)
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 {
slog.Error(gotext.Get("Error removing repo directory"), "err", err) return cliutils.FormatCliExit(gotext.Get("Error removing repo directory"), err)
os.Exit(1)
} }
err = cfg.SaveUserConfig() err = cfg.SaveUserConfig()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error saving config"), "err", err) return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
os.Exit(1)
} }
db := database.New(cfg) if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
err = db.Init(ctx) return err
if err != nil {
os.Exit(1)
} }
err = db.DeletePkgs(ctx, "repository = ?", name)
deps, err = appbuilder.
New(ctx).
UseConfig(cfg).
WithDB().
Build()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error removing packages from database"), "err", err) return err
os.Exit(1) }
defer deps.Defer()
err = deps.DB.DeletePkgs(ctx, "repository = ?", name)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error removing packages from database"), err)
} }
return nil return nil
@ -195,30 +197,22 @@ 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 utils.DropCapsToAlrUser() != nil { if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
slog.Error(gotext.Get("Can't drop privileges")) return err
os.Exit(1)
} }
ctx := c.Context ctx := c.Context
cfg := config.New()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg) deps, err := appbuilder.
err = db.Init(ctx) New(ctx).
WithConfig().
WithDB().
WithReposForcePull().
Build()
if err != nil { if err != nil {
os.Exit(1) return 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
}, },
} }

@ -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.ExitIfCantDropCapsToAlrUser(); err != nil { if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
return err return err
} }
@ -93,8 +93,7 @@ func SearchCmd() *cli.Command {
Build(), Build(),
) )
if err != nil { if err != nil {
slog.Error(gotext.Get("Error while executing search")) return cliutils.FormatCliExit(gotext.Get("Error while executing search"), err)
return cli.Exit(err, 1)
} }
format := c.String("format") format := c.String("format")
@ -102,8 +101,7 @@ 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 {
slog.Error(gotext.Get("Error parsing format template")) return cliutils.FormatCliExit(gotext.Get("Error parsing format template"), err)
return cli.Exit(err, 1)
} }
} }
@ -111,8 +109,7 @@ 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 {
slog.Error(gotext.Get("Error executing template")) return cliutils.FormatCliExit(gotext.Get("Error executing template"), err)
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/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
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,7 +38,6 @@ 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"
) )
@ -55,61 +54,59 @@ func UpgradeCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
err := utils.DropCapsToAlrUser() if err := utils.ExitIfNotRoot(); err != nil {
if err != nil { return err
slog.Error(gotext.Get("Error dropping capabilities"), "err", err)
os.Exit(1)
} }
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()
ctx := c.Context ctx := c.Context
cfg := config.New() deps, err := appbuilder.
err = cfg.Load() New(ctx).
WithConfig().
WithDB().
WithRepos().
WithDistroInfo().
WithManager().
Build()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err) return err
os.Exit(1)
} }
defer deps.Defer()
db := database.New(cfg) builder, err := build.NewMainBuilder(
rs := repos.New(cfg, db) deps.Cfg,
err = db.Init(ctx) deps.Manager,
if err != nil { deps.Repos,
slog.Error(gotext.Get("Error initialization database"), "err", err) scripter,
os.Exit(1) installer,
}
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 {
slog.Error(gotext.Get("Error parsing os-release file"), "err", err) return err
os.Exit(1)
} }
mgr := manager.Detect() updates, err := checkForUpdates(ctx, deps.Manager, deps.DB, deps.Info)
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 {
slog.Error(gotext.Get("Error pulling repos"), "err", err) return cliutils.FormatCliExit(gotext.Get("Error checking for updates"), 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 {
@ -120,14 +117,13 @@ func UpgradeCmd() *cli.Command {
Clean: c.Bool("clean"), Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"), Interactive: c.Bool("interactive"),
}, },
Info: info, Info: deps.Info,
PkgFormat_: build.GetPkgFormat(mgr), PkgFormat_: build.GetPkgFormat(deps.Manager),
}, },
updates, updates,
) )
if err != nil { if err != nil {
slog.Error(gotext.Get("Error checking for updates"), "err", err) return cliutils.FormatCliExit(gotext.Get("Error checking for updates"), err)
os.Exit(1)
} }
} else { } else {
slog.Info(gotext.Get("There is nothing to do.")) slog.Info(gotext.Get("There is nothing to do."))
@ -141,9 +137,7 @@ 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)