From 240ee852c8d02b02d94558f0b631b5f73273eb90 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Tue, 15 Apr 2025 01:32:02 +0300 Subject: [PATCH] wip --- assets/coverage-badge.svg | 4 +- build.go | 4 +- install.go | 16 ++----- internal/cliutils/app_builder/builder.go | 4 +- internal/logger/hclog.go | 14 +++++- internal/translations/default.pot | 42 ++++++++--------- internal/translations/po/ru/default.po | 45 +++++++++--------- internal/utils/cmd.go | 10 +--- pkg/build/main_build.go | 48 ++++++++++++++----- pkg/build/safe.go | 39 ++++++++++++---- pkg/build/safe_installer.go | 47 ++++++++++++++++--- upgrade.go | 59 +++++++++++------------- 12 files changed, 201 insertions(+), 131 deletions(-) diff --git a/assets/coverage-badge.svg b/assets/coverage-badge.svg index f725e1b..2ed2e71 100644 --- a/assets/coverage-badge.svg +++ b/assets/coverage-badge.svg @@ -11,7 +11,7 @@ coverage coverage - 16.0% - 16.0% + 15.9% + 15.9% diff --git a/build.go b/build.go index b988dbd..2d6e5d0 100644 --- a/build.go +++ b/build.go @@ -175,13 +175,15 @@ func BuildCmd() *cli.Command { return err } - builder, err := build.NewMainBuilder( + builder, cleanup, err := build.NewMainBuilder( deps.Cfg, + deps.Manager, deps.Repos, ) if err != nil { return err } + defer cleanup() if scriptArgs != nil { res, err = builder.BuildPackageFromScript( diff --git a/install.go b/install.go index 33ad2d0..8c466c1 100644 --- a/install.go +++ b/install.go @@ -58,30 +58,28 @@ func InstallCmd() *cli.Command { return cliutils.FormatCliExit(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()), nil) } - mgr := manager.Detect() - if mgr == nil { - return cliutils.FormatCliExit(gotext.Get("Unable to detect a supported package manager on the system"), nil) - } - deps, err := appbuilder. New(ctx). WithConfig(). WithDB(). WithReposNoPull(). WithDistroInfo(). + WithManager(). Build() if err != nil { return err } defer deps.Defer() - builder, err := build.NewMainBuilder( + builder, cleanup, err := build.NewMainBuilder( deps.Cfg, + deps.Manager, deps.Repos, ) if err != nil { return err } + defer cleanup() if deps.Cfg.AutoPull() { if err := deps.Repos.Pull(ctx, deps.Cfg.Repos()); err != nil { @@ -89,10 +87,6 @@ func InstallCmd() *cli.Command { } } - if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { - return err - } - err = builder.InstallPkgs( ctx, &build.BuildArgs{ @@ -101,7 +95,7 @@ func InstallCmd() *cli.Command { Interactive: c.Bool("interactive"), }, Info: deps.Info, - PkgFormat_: build.GetPkgFormat(mgr), + PkgFormat_: build.GetPkgFormat(deps.Manager), }, args.Slice(), ) diff --git a/internal/cliutils/app_builder/builder.go b/internal/cliutils/app_builder/builder.go index d202f4b..2989bee 100644 --- a/internal/cliutils/app_builder/builder.go +++ b/internal/cliutils/app_builder/builder.go @@ -160,8 +160,8 @@ func (b *AppBuilder) WithManager() *AppBuilder { return b } - mgr := manager.Detect() - if mgr == nil { + 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) } diff --git a/internal/logger/hclog.go b/internal/logger/hclog.go index 28a2b1e..5b55765 100644 --- a/internal/logger/hclog.go +++ b/internal/logger/hclog.go @@ -19,6 +19,7 @@ package logger import ( "io" "log" + "strings" chLog "github.com/charmbracelet/log" "github.com/hashicorp/go-hclog" @@ -55,7 +56,18 @@ func (a *HCLoggerAdapter) Log(level hclog.Level, msg string, args ...interface{} filteredArgs = append(filteredArgs, args[i], args[i+1]) } } - a.logger.l.Log(hclogLevelTochLog(level), msg, filteredArgs...) + + /* + * Start ugly hacks + */ + var chLogLevel chLog.Level + if msg == "plugin process exited" || 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{}) { diff --git a/internal/translations/default.pot b/internal/translations/default.pot index d3af001..9f0bc9b 100644 --- a/internal/translations/default.pot +++ b/internal/translations/default.pot @@ -46,15 +46,15 @@ msgstr "" msgid "Nothing to build" msgstr "" -#: build.go:199 +#: build.go:201 msgid "Error building package" msgstr "" -#: build.go:206 +#: build.go:208 msgid "Error moving the package" msgstr "" -#: build.go:210 +#: build.go:212 msgid "Done" msgstr "" @@ -158,31 +158,31 @@ msgstr "" msgid "Command install expected at least 1 argument, got %d" msgstr "" -#: install.go:63 install.go:172 install.go:222 -msgid "Unable to detect a supported package manager on the system" -msgstr "" - -#: install.go:88 +#: install.go:86 msgid "Error pulling repositories" msgstr "" -#: install.go:109 +#: install.go:103 msgid "Error parsing os release" msgstr "" -#: install.go:154 +#: install.go:148 msgid "Remove an installed package" msgstr "" -#: install.go:176 +#: install.go:166 install.go:216 +msgid "Unable to detect a supported package manager on the system" +msgstr "" + +#: install.go:170 msgid "Error listing installed packages" msgstr "" -#: install.go:217 +#: install.go:211 msgid "Command remove expected at least 1 argument, got %d" msgstr "" -#: install.go:230 +#: install.go:224 msgid "Error removing packages" msgstr "" @@ -311,15 +311,15 @@ msgstr "" msgid "ERROR" msgstr "" -#: internal/utils/cmd.go:87 +#: internal/utils/cmd.go:79 msgid "Error dropping capabilities" msgstr "" -#: internal/utils/cmd.go:94 +#: internal/utils/cmd.go:86 msgid "You need to be root to perform this action" msgstr "" -#: internal/utils/cmd.go:136 +#: internal/utils/cmd.go:128 msgid "You need to be a %s member to perform this action" msgstr "" @@ -507,18 +507,14 @@ msgstr "" msgid "Error executing template" msgstr "" -#: upgrade.go:48 +#: upgrade.go:50 msgid "Upgrade all installed packages" msgstr "" -#: upgrade.go:100 -msgid "Error pulling repos" -msgstr "" - -#: upgrade.go:106 upgrade.go:123 +#: upgrade.go:99 upgrade.go:116 msgid "Error checking for updates" msgstr "" -#: upgrade.go:126 +#: upgrade.go:119 msgid "There is nothing to do." msgstr "" diff --git a/internal/translations/po/ru/default.po b/internal/translations/po/ru/default.po index 88f5cb8..5e34b97 100644 --- a/internal/translations/po/ru/default.po +++ b/internal/translations/po/ru/default.po @@ -54,15 +54,15 @@ msgstr "Пакет не найден" msgid "Nothing to build" msgstr "Исполнение build()" -#: build.go:199 +#: build.go:201 msgid "Error building package" msgstr "Ошибка при сборке пакета" -#: build.go:206 +#: build.go:208 msgid "Error moving the package" msgstr "Ошибка при перемещении пакета" -#: build.go:210 +#: build.go:212 msgid "Done" msgstr "Сделано" @@ -171,31 +171,31 @@ msgstr "Установить новый пакет" msgid "Command install expected at least 1 argument, got %d" msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d" -#: install.go:63 install.go:172 install.go:222 -msgid "Unable to detect a supported package manager on the system" -msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" - -#: install.go:88 +#: install.go:86 msgid "Error pulling repositories" msgstr "Ошибка при извлечении репозиториев" -#: install.go:109 +#: install.go:103 msgid "Error parsing os release" msgstr "Ошибка при разборе файла выпуска операционной системы" -#: install.go:154 +#: install.go:148 msgid "Remove an installed package" msgstr "Удалить установленный пакет" -#: install.go:176 +#: install.go:166 install.go:216 +msgid "Unable to detect a supported package manager on the system" +msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" + +#: install.go:170 msgid "Error listing installed packages" msgstr "Ошибка при составлении списка установленных пакетов" -#: install.go:217 +#: install.go:211 msgid "Command remove expected at least 1 argument, got %d" msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d" -#: install.go:230 +#: install.go:224 msgid "Error removing packages" msgstr "Ошибка при удалении пакетов" @@ -326,16 +326,16 @@ msgstr "%s %s загружается — %s/с\n" msgid "ERROR" msgstr "ОШИБКА" -#: internal/utils/cmd.go:87 +#: internal/utils/cmd.go:79 #, fuzzy msgid "Error dropping capabilities" msgstr "Ошибка при открытии базы данных" -#: internal/utils/cmd.go:94 +#: internal/utils/cmd.go:86 msgid "You need to be root to perform this action" msgstr "" -#: internal/utils/cmd.go:136 +#: internal/utils/cmd.go:128 msgid "You need to be a %s member to perform this action" msgstr "" @@ -533,22 +533,21 @@ msgstr "Ошибка при разборе шаблона" msgid "Error executing template" msgstr "Ошибка при выполнении шаблона" -#: upgrade.go:48 +#: upgrade.go:50 msgid "Upgrade all installed packages" msgstr "Обновить все установленные пакеты" -#: upgrade.go:100 -msgid "Error pulling repos" -msgstr "Ошибка при извлечении репозиториев" - -#: upgrade.go:106 upgrade.go:123 +#: upgrade.go:99 upgrade.go:116 msgid "Error checking for updates" msgstr "Ошибка при проверке обновлений" -#: upgrade.go:126 +#: upgrade.go:119 msgid "There is nothing to do." msgstr "Здесь нечего делать." +#~ msgid "Error pulling repos" +#~ msgstr "Ошибка при извлечении репозиториев" + #, fuzzy #~ msgid "Error getting current executable" #~ msgstr "Ошибка при получении рабочего каталога" diff --git a/internal/utils/cmd.go b/internal/utils/cmd.go index 1698312..988afcc 100644 --- a/internal/utils/cmd.go +++ b/internal/utils/cmd.go @@ -70,15 +70,7 @@ func DropCapsToAlrUser() error { 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 + return EnuseIsAlrUser() } func ExitIfCantDropCapsToAlrUser() cli.ExitCoder { diff --git a/pkg/build/main_build.go b/pkg/build/main_build.go index f23c188..702e130 100644 --- a/pkg/build/main_build.go +++ b/pkg/build/main_build.go @@ -18,6 +18,8 @@ package build import ( "log/slog" + "sync" + "syscall" "gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" @@ -25,28 +27,48 @@ import ( func NewMainBuilder( cfg Config, + mgr manager.Manager, repos PackageFinder, -) (*Builder, error) { - installerExecutor, err := GetSafeInstaller() +) (*Builder, func(), error) { + var err error + + var safeInstallerClose, safeScriptExecutorClose func() + + var cleanupOnce sync.Once + cleanup := func() { + cleanupOnce.Do(func() { + if safeScriptExecutorClose != nil { + safeScriptExecutorClose() + } + if safeInstallerClose != nil { + safeInstallerClose() + } + }) + } + + defer func() { + if err != nil { + slog.Debug("close executors") + cleanup() + } + }() + + installerExecutor, safeInstallerClose, err := GetSafeInstaller() if err != nil { - slog.Error("i will panic GetSafeInstaller", "err", err) - return nil, err + return nil, 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 + if err = utils.NoNewPrivs(); err != nil { + return nil, nil, err } - s, err := GetSafeScriptExecutor() + s, safeScriptExecutorClose, err := GetSafeScriptExecutor() if err != nil { - slog.Error("i will panic GetSafeScriptExecutor", "err", err) - return nil, err + return nil, nil, err } - mgr := manager.Detect() - builder := &Builder{ scriptExecutor: s, cacheExecutor: &Cache{ @@ -68,5 +90,7 @@ func NewMainBuilder( repos: repos, } - return builder, nil + slog.Warn("uid", "uid", syscall.Getuid(), "gid", syscall.Getgid()) + + return builder, cleanup, nil } diff --git a/pkg/build/safe.go b/pkg/build/safe.go index edf34ec..75201e4 100644 --- a/pkg/build/safe.go +++ b/pkg/build/safe.go @@ -18,10 +18,12 @@ package build import ( "context" + "fmt" "log/slog" "net/rpc" "os" "os/exec" + "sync" "syscall" "github.com/hashicorp/go-plugin" @@ -217,10 +219,12 @@ var pluginMap = map[string]plugin.Plugin{ "installer": &InstallerPlugin{}, } -func GetSafeScriptExecutor() (ScriptExecutor, error) { +func GetSafeScriptExecutor() (ScriptExecutor, func(), error) { + var err error + executable, err := os.Executable() if err != nil { - return nil, err + return nil, nil, err } cmd := exec.Command(executable, "_internal-safe-script-executor") @@ -233,7 +237,7 @@ func GetSafeScriptExecutor() (ScriptExecutor, error) { } uid, gid, err := utils.GetUidGidAlrUser() if err != nil { - return nil, err + return nil, nil, err } cmd.SysProcAttr = &syscall.SysProcAttr{ Credential: &syscall.Credential{ @@ -254,14 +258,33 @@ func GetSafeScriptExecutor() (ScriptExecutor, error) { }) rpcClient, err := client.Client() if err != nil { - slog.Info("1") - return nil, err + return nil, nil, err } - raw1, err := rpcClient.Dispense("script-executor") + var cleanupOnce sync.Once + cleanup := func() { + cleanupOnce.Do(func() { + client.Kill() + }) + } + + defer func() { + if err != nil { + slog.Debug("close script-executor") + cleanup() + } + }() + + raw, err := rpcClient.Dispense("script-executor") if err != nil { - return nil, err + 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 } diff --git a/pkg/build/safe_installer.go b/pkg/build/safe_installer.go index 09bb0ff..bf1ffd9 100644 --- a/pkg/build/safe_installer.go +++ b/pkg/build/safe_installer.go @@ -17,15 +17,18 @@ package build import ( + "fmt" "log/slog" "net/rpc" "os" "os/exec" + "sync" "syscall" "github.com/hashicorp/go-plugin" "gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" + "gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" ) type InstallerPlugin struct { @@ -80,10 +83,12 @@ func (p *InstallerPlugin) Server(*plugin.MuxBroker) (interface{}, error) { return &InstallerRPCServer{Impl: p.Impl}, nil } -func GetSafeInstaller() (InstallerExecutor, error) { +func GetSafeInstaller() (InstallerExecutor, func(), error) { + var err error + executable, err := os.Executable() if err != nil { - return nil, err + return nil, nil, err } cmd := exec.Command(executable, "_internal-installer") cmd.Env = append(os.Environ(), @@ -94,6 +99,16 @@ func GetSafeInstaller() (InstallerExecutor, error) { "ALR_LOG_LEVEL=DEBUG", "XDG_SESSION_CLASS=user", ) + uid, gid, err := utils.GetUidGidAlrUser() + if err != nil { + return nil, nil, err + } + cmd.SysProcAttr = &syscall.SysProcAttr{ + Credential: &syscall.Credential{ + Uid: uint32(uid), + Gid: uint32(gid), + }, + } slog.Debug("safe installer setup", "uid", syscall.Getuid(), "gid", syscall.Getgid()) @@ -110,13 +125,33 @@ func GetSafeInstaller() (InstallerExecutor, error) { }) rpcClient, err := client.Client() if err != nil { - return nil, err + return nil, nil, err } - raw1, err := rpcClient.Dispense("installer") + var cleanupOnce sync.Once + cleanup := func() { + cleanupOnce.Do(func() { + client.Kill() + }) + } + + defer func() { + if err != nil { + slog.Debug("close installer") + cleanup() + } + }() + + raw, err := rpcClient.Dispense("installer") if err != nil { - return nil, err + 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 } diff --git a/upgrade.go b/upgrade.go index b45c27b..dfd39f5 100644 --- a/upgrade.go +++ b/upgrade.go @@ -23,6 +23,7 @@ import ( "context" "fmt" "log/slog" + "syscall" "github.com/leonelquinteros/gotext" "github.com/urfave/cli/v2" @@ -30,6 +31,7 @@ import ( "golang.org/x/exp/maps" "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" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" @@ -55,53 +57,44 @@ func UpgradeCmd() *cli.Command { }, }, Action: func(c *cli.Context) error { - err := utils.DropCapsToAlrUser() - if err != nil { - return cliutils.FormatCliExit(gotext.Get("Error dropping capabilities"), err) + if err := utils.ExitIfNotRoot(); err != nil { + return err } ctx := c.Context - cfg := config.New() - err = cfg.Load() + deps, err := appbuilder. + New(ctx). + WithConfig(). + WithDB(). + WithReposNoPull(). + WithDistroInfo(). + WithManager(). + Build() if err != nil { - return cliutils.FormatCliExit(gotext.Get("Error loading config"), err) + return err } + defer deps.Defer() - db := database.New(cfg) - rs := repos.New(cfg, db) - err = db.Init(ctx) - if err != nil { - return cliutils.FormatCliExit(gotext.Get("Error initialization database"), err) - } - - builder, err := build.NewMainBuilder( - cfg, - rs, + builder, cleanup, err := build.NewMainBuilder( + deps.Cfg, + deps.Manager, + deps.Repos, ) if err != nil { return err } + defer cleanup() - info, err := distro.ParseOSRelease(ctx) - slog.Debug("ParseOSRelease", "err", err) - if err != nil { - return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) - } + slog.Warn("", "uid", syscall.Getuid(), "gid", syscall.Getgid()) - mgr := manager.Detect() - if mgr == nil { - return cliutils.FormatCliExit(gotext.Get("Unable to detect a supported package manager on the system"), nil) - } - - if cfg.AutoPull() { - err = rs.Pull(ctx, cfg.Repos()) - if err != nil { - return cliutils.FormatCliExit(gotext.Get("Error pulling repos"), 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) } } - updates, err := checkForUpdates(ctx, mgr, cfg, db, rs, info) + updates, err := checkForUpdates(ctx, deps.Manager, deps.Cfg, deps.DB, deps.Repos, deps.Info) if err != nil { return cliutils.FormatCliExit(gotext.Get("Error checking for updates"), err) } @@ -114,8 +107,8 @@ func UpgradeCmd() *cli.Command { Clean: c.Bool("clean"), Interactive: c.Bool("interactive"), }, - Info: info, - PkgFormat_: build.GetPkgFormat(mgr), + Info: deps.Info, + PkgFormat_: build.GetPkgFormat(deps.Manager), }, updates, )