This commit is contained in:
Maxim Slipenko 2025-04-14 23:35:10 +03:00
parent e3aaa88822
commit 6d5016270f
21 changed files with 449 additions and 349 deletions

@ -20,10 +20,8 @@
package main package main
import ( import (
"bytes"
"log/slog" "log/slog"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
@ -31,15 +29,13 @@ import (
"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/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/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 BuildCmd() *cli.Command { func BuildCmd() *cli.Command {
@ -74,53 +70,25 @@ func BuildCmd() *cli.Command {
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err) return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err)
} }
executable, err := os.Executable()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err)
}
cmd := exec.Command(executable, "_internal-mount", wd) wd, cleanup, err := Mount(wd)
var stdout bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err) return err
}
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()
}()
err = utils.DropCapsToAlrUser()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error dropping capabilities"), err)
}
_, err = os.Stat(wd)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error dropping capabilities"), err)
} }
defer cleanup()
ctx := c.Context ctx := c.Context
cfg := config.New()
err = cfg.Load()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error loading config"), err)
}
db := database.New(cfg) deps, err := appbuilder.
rs := repos.New(cfg, db) New(ctx).
err = db.Init(ctx) WithConfig().
WithDB().
WithReposNoPull().
Build()
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error initialization database"), err) return cli.Exit(err, 1)
} }
defer deps.Defer()
var script string var script string
var packages []string var packages []string
@ -137,10 +105,17 @@ func BuildCmd() *cli.Command {
return cliutils.FormatCliExit(gotext.Get("Error parsing os release"), err) return cliutils.FormatCliExit(gotext.Get("Error parsing os release"), err)
} }
builder := build.NewMainBuilder( if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
cfg, return err
rs, }
builder, err := build.NewMainBuilder(
deps.Cfg,
deps.Repos,
) )
if err != nil {
return err
}
var res *build.BuildResult var res *build.BuildResult
@ -179,7 +154,7 @@ func BuildCmd() *cli.Command {
packageSearch = arr[0] packageSearch = arr[0]
} }
pkgs, _, _ := rs.FindPkgs(ctx, []string{packageSearch}) pkgs, _, _ := deps.Repos.FindPkgs(ctx, []string{packageSearch})
pkg, ok := pkgs[packageSearch] pkg, ok := pkgs[packageSearch]
if len(pkg) < 1 || !ok { if len(pkg) < 1 || !ok {
slog.Error(gotext.Get("Package not found")) slog.Error(gotext.Get("Package not found"))

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=

@ -21,22 +21,18 @@ package main
import ( import (
"fmt" "fmt"
"log/slog"
"os"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/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/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/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 {
@ -52,50 +48,51 @@ func InstallCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfNotRoot(); err != nil {
return err
}
ctx := c.Context ctx := c.Context
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() mgr := manager.Detect()
if mgr == nil { if mgr == nil {
slog.Error(gotext.Get("Unable to detect a supported package manager on the system")) return cliutils.FormatCliExit(gotext.Get("Unable to detect a supported package manager on the system"), nil)
os.Exit(1)
} }
cfg := config.New() deps, err := appbuilder.
err := cfg.Load() New(ctx).
WithConfig().
WithDB().
WithReposNoPull().
Build()
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error loading config"), err) return err
} }
defer deps.Defer()
db := database.New(cfg) builder, err := build.NewMainBuilder(
rs := repos.New(cfg, db) deps.Cfg,
err = db.Init(ctx) deps.Repos,
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error initialization database"), err)
}
err = utils.DropCapsToAlrUser()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error dropping capabilities"), err)
}
builder := build.NewMainBuilder(
cfg,
rs,
) )
if cfg.AutoPull() {
err := rs.Pull(ctx, cfg.Repos())
if err != nil { if err != nil {
return err
}
if deps.Cfg.AutoPull() {
if err := deps.Repos.Pull(ctx, deps.Cfg.Repos()); err != nil {
return cliutils.FormatCliExit(gotext.Get("Error pulling repositories"), err) return cliutils.FormatCliExit(gotext.Get("Error pulling repositories"), err)
} }
} }
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error parsing os release"), err) return cliutils.FormatCliExit(gotext.Get("Error parsing os release"), err)
@ -120,18 +117,22 @@ func InstallCmd() *cli.Command {
return nil return nil
}, },
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { 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 {
return cliutils.FormatCliExit(gotext.Get("Error loading config"), err)
} }
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 {
return cliutils.FormatCliExit(gotext.Get("Error initialization database"), err) return err
} }
result, err := db.GetPkgs(c.Context, "true") defer deps.Defer()
result, err := deps.DB.GetPkgs(c.Context, "true")
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
} }
@ -173,8 +174,7 @@ func RemoveCmd() *cli.Command {
installedAlrPackages := map[string]string{} installedAlrPackages := map[string]string{}
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 cliutils.FormatCliExit(gotext.Get("Unable to detect a supported package manager on the system"), nil)
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 {

@ -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"
@ -32,13 +33,13 @@ import (
"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/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 {
@ -48,13 +49,15 @@ 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 {
return cliutils.FormatCliExit(gotext.Get("Error loading config"), err) return cliutils.FormatCliExit(gotext.Get("Error loading config"), err)
} }
@ -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,24 +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.EnuseIsAlrUser(); 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 {
return cliutils.FormatCliExit(gotext.Get("Error loading config"), err) return cliutils.FormatCliExit("cannot escalate to root", err)
} }
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 {
return cliutils.FormatCliExit(gotext.Get("Error initialization database"), err) return err
} }
defer deps.Defer()
logger := hclog.New(&hclog.LoggerOptions{ logger := hclog.New(&hclog.LoggerOptions{
Name: "plugin", Name: "plugin",
@ -119,7 +127,7 @@ func InternalInstallCmd() *cli.Command {
Plugins: map[string]plugin.Plugin{ Plugins: map[string]plugin.Plugin{
"installer": &build.InstallerPlugin{ "installer": &build.InstallerPlugin{
Impl: build.NewInstaller( Impl: build.NewInstaller(
rs, deps.Repos,
manager.Detect(), manager.Detect(),
), ),
}, },
@ -131,52 +139,100 @@ 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, _ := user.Current()
_, alrGid, _ := utils.GetUidGidAlrUser()
logger.SetupForGoPlugin() if err := utils.EnuseIsWheelMember(); err != nil {
err := syscall.Setuid(0) return err
if err != nil {
slog.Error("Failed to setuid(0)", "err", err)
os.Exit(1)
} }
alrRunDir := "/var/run/alr" // Before escalating the rights, we made sure that
err = os.MkdirAll(alrRunDir, 0o750) // 1. user in wheel group
if err != nil { // 2. user can access sourceDir
slog.Error("Error creating /var/run/alr directory", "err", err) if err := utils.EscalateToRootUid(); err != nil {
os.Exit(1) return err
}
if err := syscall.Setgid(alrGid); err != nil {
return err
} }
_, gid, _ := utils.GetUidGidAlrUser() if err := os.MkdirAll(constants.AlrRunDir, 0o770); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("failed to create %s", constants.AlrRunDir), 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 := os.Chown(constants.AlrRunDir, 0, alrGid); err != nil {
targetDir := filepath.Join(alrRunDir, fmt.Sprintf("bindfs-%d", os.Getpid())) return cliutils.FormatCliExit(fmt.Sprintf("failed to chown %s", constants.AlrRunDir), 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) targetDir := filepath.Join(constants.AlrRunDir, fmt.Sprintf("bindfs-%d", os.Getpid()))
err = os.Chown(targetDir, 0, gid) // 0750: owner (root) and group (alr)
if err != nil { if err := os.MkdirAll(targetDir, 0o750); err != nil {
slog.Error("Failed to chown bindfs directory", "err", err) return cliutils.FormatCliExit("error creating bindfs target directory", err)
os.Exit(1) }
// 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(
@ -188,74 +244,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,8 +22,8 @@ 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/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
@ -68,8 +68,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
} }
@ -90,8 +89,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
} }
@ -130,8 +128,7 @@ func (b *AppBuilder) withRepos(enablePull, forcePull bool) *AppBuilder {
if enablePull && (forcePull || 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
} }
} }

@ -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,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 constants
const (
SystemConfigPath = "/etc/alr/alr.toml"
SystemCachePath = "/var/cache/alr"
AlrRunDir = "/var/run/alr"
)

@ -9,64 +9,52 @@ 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:48 #: build.go:44
msgid "Build a local package" msgid "Build a local package"
msgstr "" msgstr ""
#: build.go:54 #: build.go:50
msgid "Path to the build script" msgid "Path to the build script"
msgstr "" msgstr ""
#: build.go:59 #: build.go:55
msgid "Specify subpackage in script (for multi package script only)" msgid "Specify subpackage in script (for multi package script only)"
msgstr "" msgstr ""
#: build.go:64 #: build.go:60
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:69 #: build.go:65
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:75 build.go:79 build.go:88 #: build.go:71
msgid "Error getting working directory" msgid "Error getting working directory"
msgstr "" msgstr ""
#: build.go:104 build.go:108 #: build.go:99
msgid "Error dropping capabilities"
msgstr ""
#: build.go:115
msgid "Error loading config"
msgstr ""
#: build.go:122
msgid "Error initialization database"
msgstr ""
#: build.go:131
msgid "Unable to detect a supported package manager on the system" msgid "Unable to detect a supported package manager on the system"
msgstr "" msgstr ""
#: build.go:137 #: build.go:105
msgid "Error parsing os release" msgid "Error parsing os release"
msgstr "" msgstr ""
#: build.go:168 build.go:209 #: build.go:143 build.go:184
msgid "Error building package" msgid "Error building package"
msgstr "" msgstr ""
#: build.go:185 #: build.go:160
msgid "Package not found" msgid "Package not found"
msgstr "" msgstr ""
#: build.go:212 #: build.go:187
msgid "Nothing to build" msgid "Nothing to build"
msgstr "" msgstr ""
#: build.go:221 #: build.go:196
msgid "Error moving the package" msgid "Error moving the package"
msgstr "" msgstr ""
@ -166,7 +154,7 @@ msgstr ""
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "" msgstr ""
#: install.go:45 #: install.go:41
msgid "Install a new package" msgid "Install a new package"
msgstr "" msgstr ""
@ -174,11 +162,11 @@ msgstr ""
msgid "Command install expected at least 1 argument, got %d" msgid "Command install expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: install.go:95 #: install.go:88
msgid "Error pulling repositories" msgid "Error pulling repositories"
msgstr "" msgstr ""
#: install.go:158 #: install.go:159
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "" msgstr ""
@ -194,6 +182,14 @@ msgstr ""
msgid "Error removing packages" msgid "Error removing packages"
msgstr "" msgstr ""
#: internal/cliutils/app_builder/builder.go:71
msgid "Error loading config"
msgstr ""
#: internal/cliutils/app_builder/builder.go:92
msgid "Error initialization database"
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,7 +307,11 @@ msgstr ""
msgid "ERROR" msgid "ERROR"
msgstr "" msgstr ""
#: internal/utils/cmd.go:94 #: internal/utils/cmd.go:86
msgid "Error dropping capabilities"
msgstr ""
#: internal/utils/cmd.go:93
msgid "You need to be root to perform this action" msgid "You need to be root to perform this action"
msgstr "" msgstr ""
@ -331,27 +331,27 @@ msgstr ""
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "" msgstr ""
#: main.go:148 #: main.go:147
msgid "Show help" msgid "Show help"
msgstr "" msgstr ""
#: main.go:152 #: 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,47 +419,47 @@ msgid ""
"updating ALR if something doesn't work." "updating ALR if something doesn't work."
msgstr "" msgstr ""
#: repo.go:40 #: repo.go:39
msgid "Add a new repository" msgid "Add a new repository"
msgstr "" msgstr ""
#: repo.go:47 #: repo.go:46
msgid "Name of the new repo" msgid "Name of the new repo"
msgstr "" msgstr ""
#: repo.go:53 #: repo.go:52
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "" msgstr ""
#: repo.go:80 #: repo.go:79
msgid "Repo %s already exists" msgid "Repo %s already exists"
msgstr "" msgstr ""
#: repo.go:91 repo.go:169 #: repo.go:90 repo.go:167
msgid "Error saving config" msgid "Error saving config"
msgstr "" msgstr ""
#: repo.go:117 #: repo.go:116
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "" msgstr ""
#: repo.go:124 #: repo.go:123
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "" msgstr ""
#: repo.go:157 #: repo.go:156
msgid "Repo does not exist" msgid "Repo \"%s\" does not exist"
msgstr "" msgstr ""
#: repo.go:165 #: repo.go:163
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "" msgstr ""
#: repo.go:188 #: repo.go:186
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "" msgstr ""
#: repo.go:199 #: repo.go:197
msgid "Pull all repositories that have changed" msgid "Pull all repositories that have changed"
msgstr "" msgstr ""
@ -503,14 +503,14 @@ msgstr ""
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "" msgstr ""
#: upgrade.go:101 #: upgrade.go:103
msgid "Error pulling repos" msgid "Error pulling repos"
msgstr "" msgstr ""
#: upgrade.go:107 upgrade.go:124 #: upgrade.go:109 upgrade.go:126
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "" msgstr ""
#: upgrade.go:127 #: upgrade.go:129
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "" msgstr ""

@ -16,67 +16,53 @@ 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:48 #: build.go:44
msgid "Build a local package" msgid "Build a local package"
msgstr "Сборка локального пакета" msgstr "Сборка локального пакета"
#: build.go:54 #: build.go:50
msgid "Path to the build script" msgid "Path to the build script"
msgstr "Путь к скрипту сборки" msgstr "Путь к скрипту сборки"
#: build.go:59 #: build.go:55
msgid "Specify subpackage in script (for multi package script only)" msgid "Specify subpackage in script (for multi package script only)"
msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)" msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)"
#: build.go:64 #: build.go:60
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:69 #: build.go:65
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:75 build.go:79 build.go:88 #: build.go:71
msgid "Error getting working directory" msgid "Error getting working directory"
msgstr "Ошибка при получении рабочего каталога" msgstr "Ошибка при получении рабочего каталога"
#: build.go:104 build.go:108 #: build.go:99
#, fuzzy
msgid "Error dropping capabilities"
msgstr "Ошибка при открытии базы данных"
#: build.go:115
#, fuzzy
msgid "Error loading config"
msgstr "Ошибка при кодировании конфигурации"
#: build.go:122
msgid "Error initialization database"
msgstr "Ошибка инициализации базы данных"
#: build.go:131
msgid "Unable to detect a supported package manager on the system" msgid "Unable to detect a supported package manager on the system"
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе"
#: build.go:137 #: build.go:105
msgid "Error parsing os release" msgid "Error parsing os release"
msgstr "Ошибка при разборе файла выпуска операционной системы" msgstr "Ошибка при разборе файла выпуска операционной системы"
#: build.go:168 build.go:209 #: build.go:143 build.go:184
msgid "Error building package" msgid "Error building package"
msgstr "Ошибка при сборке пакета" msgstr "Ошибка при сборке пакета"
#: build.go:185 #: build.go:160
msgid "Package not found" msgid "Package not found"
msgstr "Пакет не найден" msgstr "Пакет не найден"
#: build.go:212 #: build.go:187
#, fuzzy #, fuzzy
msgid "Nothing to build" msgid "Nothing to build"
msgstr "Исполнение build()" msgstr "Исполнение build()"
#: build.go:221 #: build.go:196
msgid "Error moving the package" msgid "Error moving the package"
msgstr "Ошибка при перемещении пакета" msgstr "Ошибка при перемещении пакета"
@ -181,7 +167,7 @@ msgstr "Ошибка устранения переорпеделений"
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "Ошибка кодирования переменных скрита" msgstr "Ошибка кодирования переменных скрита"
#: install.go:45 #: install.go:41
msgid "Install a new package" msgid "Install a new package"
msgstr "Установить новый пакет" msgstr "Установить новый пакет"
@ -189,11 +175,11 @@ msgstr "Установить новый пакет"
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:95 #: install.go:88
msgid "Error pulling repositories" msgid "Error pulling repositories"
msgstr "Ошибка при извлечении репозиториев" msgstr "Ошибка при извлечении репозиториев"
#: install.go:158 #: install.go:159
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "Удалить установленный пакет" msgstr "Удалить установленный пакет"
@ -209,6 +195,15 @@ msgstr "Для команды remove ожидался хотя бы 1 аргум
msgid "Error removing packages" msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов" msgstr "Ошибка при удалении пакетов"
#: internal/cliutils/app_builder/builder.go:71
#, fuzzy
msgid "Error loading config"
msgstr "Ошибка при кодировании конфигурации"
#: internal/cliutils/app_builder/builder.go:92
msgid "Error initialization database"
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,7 +322,12 @@ msgstr "%s %s загружается — %s/с\n"
msgid "ERROR" msgid "ERROR"
msgstr "ОШИБКА" msgstr "ОШИБКА"
#: internal/utils/cmd.go:94 #: internal/utils/cmd.go:86
#, fuzzy
msgid "Error dropping capabilities"
msgstr "Ошибка при открытии базы данных"
#: internal/utils/cmd.go:93
msgid "You need to be root to perform this action" msgid "You need to be root to perform this action"
msgstr "" msgstr ""
@ -347,27 +347,27 @@ msgstr "Аргументы, которые будут переданы мене
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "Включение интерактивных вопросов и запросов" msgstr "Включение интерактивных вопросов и запросов"
#: main.go:148 #: main.go:147
msgid "Show help" msgid "Show help"
msgstr "Показать справку" msgstr "Показать справку"
#: main.go:152 #: 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,49 +441,50 @@ msgstr ""
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте " "Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
"обновить ALR, если что-то не работает." "обновить ALR, если что-то не работает."
#: repo.go:40 #: repo.go:39
msgid "Add a new repository" msgid "Add a new repository"
msgstr "Добавить новый репозиторий" msgstr "Добавить новый репозиторий"
#: repo.go:47 #: repo.go:46
msgid "Name of the new repo" msgid "Name of the new repo"
msgstr "Название нового репозитория" msgstr "Название нового репозитория"
#: repo.go:53 #: repo.go:52
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "URL-адрес нового репозитория" msgstr "URL-адрес нового репозитория"
#: repo.go:80 #: repo.go:79
#, fuzzy #, fuzzy
msgid "Repo %s already exists" msgid "Repo %s already exists"
msgstr "Репозитория не существует" msgstr "Репозитория не существует"
#: repo.go:91 repo.go:169 #: repo.go:90 repo.go:167
#, fuzzy #, fuzzy
msgid "Error saving config" msgid "Error saving config"
msgstr "Ошибка при кодировании конфигурации" msgstr "Ошибка при кодировании конфигурации"
#: repo.go:117 #: repo.go:116
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "Удалить существующий репозиторий" msgstr "Удалить существующий репозиторий"
#: repo.go:124 #: repo.go:123
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "Название репозитория удалён" msgstr "Название репозитория удалён"
#: repo.go:157 #: repo.go:156
msgid "Repo does not exist" #, fuzzy
msgid "Repo \"%s\" does not exist"
msgstr "Репозитория не существует" msgstr "Репозитория не существует"
#: repo.go:165 #: repo.go:163
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "Ошибка при удалении каталога репозитория" msgstr "Ошибка при удалении каталога репозитория"
#: repo.go:188 #: repo.go:186
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "Ошибка при удалении пакетов из базы данных" msgstr "Ошибка при удалении пакетов из базы данных"
#: repo.go:199 #: repo.go:197
msgid "Pull all repositories that have changed" msgid "Pull all repositories that have changed"
msgstr "Скачать все изменённые репозитории" msgstr "Скачать все изменённые репозитории"
@ -528,18 +529,26 @@ msgstr "Ошибка при выполнении шаблона"
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "Обновить все установленные пакеты" msgstr "Обновить все установленные пакеты"
#: upgrade.go:101 #: upgrade.go:103
msgid "Error pulling repos" msgid "Error pulling repos"
msgstr "Ошибка при извлечении репозиториев" msgstr "Ошибка при извлечении репозиториев"
#: upgrade.go:107 upgrade.go:124 #: upgrade.go:109 upgrade.go:126
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "Ошибка при проверке обновлений" msgstr "Ошибка при проверке обновлений"
#: upgrade.go:127 #: upgrade.go:129
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "Здесь нечего делать." 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"

@ -80,7 +80,6 @@ func DropCapsToAlrUser() error {
return nil return nil
} }
// Returns cli.Exit to
func ExitIfCantDropCapsToAlrUser() cli.ExitCoder { func ExitIfCantDropCapsToAlrUser() cli.ExitCoder {
err := DropCapsToAlrUser() err := DropCapsToAlrUser()
if err != nil { if err != nil {
@ -95,3 +94,63 @@ func ExitIfNotRoot() error {
} }
return nil return nil
} }
func EnuseIsAlrUser() error {
uid, gid, err := GetUidGidAlrUser()
if err != nil {
return err
}
newUid := syscall.Getuid()
if newUid != uid {
return errors.New("new uid don't matches requested")
}
newGid := syscall.Getgid()
if newGid != gid {
return errors.New("new gid don't matches requested")
}
return nil
}
func EnuseIsWheelMember() error {
currentUser, err := user.Current()
if err != nil {
return err
}
group, err := user.LookupGroup("wheel")
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 errors.New("looks like is not wheel member")
}
func EscalateToRootGid() error {
return syscall.Setgid(0)
}
func EscalateToRootUid() error {
return syscall.Setuid(0)
}
func EscalateToRoot() error {
err := EscalateToRootUid()
if err != nil {
return err
}
err = EscalateToRootGid()
if err != nil {
return err
}
return nil
}

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)
}

@ -86,7 +86,6 @@ func GetApp() *cli.App {
InternalBuildCmd(), InternalBuildCmd(),
InternalInstallCmd(), InternalInstallCmd(),
InternalMountCmd(), InternalMountCmd(),
InternalUnmountCmd(),
}, },
Before: func(c *cli.Context) error { Before: func(c *cli.Context) error {
if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" { if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" {

@ -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,

@ -19,27 +19,34 @@ package build
import ( import (
"log/slog" "log/slog"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"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,
repos PackageFinder, repos PackageFinder,
) *Builder { ) (*Builder, error) {
installerExecutor, err := GetSafeInstaller()
if err != nil {
slog.Error("i will panic GetSafeInstaller", "err", err)
return nil, err
}
// It is very important!
// See https://stackoverflow.com/questions/47296408/cannot-open-uid-map-for-writing-from-an-app-with-cap-setuid-capability-set
if err := utils.NoNewPrivs(); err != nil {
return nil, err
}
s, err := GetSafeScriptExecutor() s, err := GetSafeScriptExecutor()
if err != nil { if err != nil {
slog.Info("i will panic") slog.Error("i will panic GetSafeScriptExecutor", "err", err)
panic(err) return nil, err
} }
mgr := manager.Detect() mgr := manager.Detect()
installerExecutor, err := GetSafeInstaller()
if err != nil {
slog.Info("i will panic")
panic(err)
}
builder := &Builder{ builder := &Builder{
scriptExecutor: s, scriptExecutor: s,
cacheExecutor: &Cache{ cacheExecutor: &Cache{
@ -61,5 +68,5 @@ func NewMainBuilder(
repos: repos, repos: repos,
} }
return builder return builder, nil
} }

@ -229,6 +229,7 @@ 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 {
@ -247,6 +248,9 @@ 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 {

@ -26,7 +26,6 @@ import (
"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 +45,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 +58,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 {
@ -95,16 +94,8 @@ func GetSafeInstaller() (InstallerExecutor, error) {
"ALR_LOG_LEVEL=DEBUG", "ALR_LOG_LEVEL=DEBUG",
"XDG_SESSION_CLASS=user", "XDG_SESSION_CLASS=user",
) )
uid, gid, err := utils.GetUidGidAlrUser()
if err != nil { slog.Debug("safe installer setup", "uid", syscall.Getuid(), "gid", syscall.Getgid())
return nil, err
}
cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{
Uid: uint32(uid),
Gid: uint32(gid),
},
}
client := plugin.NewClient(&plugin.ClientConfig{ client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: HandshakeConfig, HandshakeConfig: HandshakeConfig,
@ -119,7 +110,6 @@ func GetSafeInstaller() (InstallerExecutor, error) {
}) })
rpcClient, err := client.Client() rpcClient, err := client.Client()
if err != nil { if err != nil {
slog.Info("1")
return nil, err return nil, err
} }

@ -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
} }

@ -20,7 +20,6 @@
package main package main
import ( import (
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
@ -76,7 +75,7 @@ func AddRepoCmd() *cli.Command {
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 {
return cliutils.FormatCliExit(gotext.Get("Repo %s already exists", repo.Name), nil) return cliutils.FormatCliExit(gotext.Get("Repo %s already exists", repo.Name), nil)
} }
} }
@ -154,8 +153,7 @@ 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))

@ -76,11 +76,13 @@ func UpgradeCmd() *cli.Command {
return cliutils.FormatCliExit(gotext.Get("Error initialization database"), err) return cliutils.FormatCliExit(gotext.Get("Error initialization database"), err)
} }
slog.Debug("builder setup") builder, err := build.NewMainBuilder(
builder := build.NewMainBuilder(
cfg, cfg,
rs, rs,
) )
if err != nil {
return err
}
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
slog.Debug("ParseOSRelease", "err", err) slog.Debug("ParseOSRelease", "err", err)