1
0
forked from Plemya-x/ALR
ALR/internal.go

281 lines
7.2 KiB
Go

// 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 main
import (
"bufio"
"errors"
"fmt"
"log/slog"
"os"
"os/exec"
"os/user"
"path/filepath"
"syscall"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/leonelquinteros/gotext"
"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/constants"
"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/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
)
func InternalBuildCmd() *cli.Command {
return &cli.Command{
Name: "_internal-safe-script-executor",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
logger.SetupForGoPlugin()
slog.Debug("start _internal-safe-script-executor", "uid", syscall.Getuid(), "gid", syscall.Getgid())
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
cfg := config.New()
err := cfg.Load()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error loading config"), err)
}
logger := hclog.New(&hclog.LoggerOptions{
Name: "plugin",
Output: os.Stderr,
Level: hclog.Debug,
JSONFormat: false,
DisableTime: true,
})
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: build.HandshakeConfig,
Plugins: map[string]plugin.Plugin{
"script-executor": &build.ScriptExecutorPlugin{
Impl: build.NewLocalScriptExecutor(cfg),
},
},
Logger: logger,
})
return nil
},
}
}
func InternalInstallCmd() *cli.Command {
return &cli.Command{
Name: "_internal-installer",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
logger.SetupForGoPlugin()
if err := utils.EnsureIsAlrUser(); err != nil {
return err
}
// Before escalating the rights, we made sure that
// this is an ALR user, so it looks safe.
err := utils.EscalateToRootUid()
if err != nil {
return cliutils.FormatCliExit("cannot escalate to root", err)
}
deps, err := appbuilder.
New(c.Context).
WithConfig().
WithDB().
WithReposNoPull().
Build()
if err != nil {
return err
}
defer deps.Defer()
logger := hclog.New(&hclog.LoggerOptions{
Name: "plugin",
Output: os.Stderr,
Level: hclog.Trace,
JSONFormat: true,
DisableTime: true,
})
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: build.HandshakeConfig,
Plugins: map[string]plugin.Plugin{
"installer": &build.InstallerPlugin{
Impl: build.NewInstaller(
manager.Detect(),
),
},
},
Logger: logger,
})
return nil
},
}
}
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 {
return &cli.Command{
Name: "_internal-temporary-mount",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
logger.SetupForGoPlugin()
sourceDir := c.Args().First()
u, err := user.Current()
if err != nil {
return cliutils.FormatCliExit("cannot get current user", err)
}
_, alrGid, err := utils.GetUidGidAlrUser()
if err != nil {
return cliutils.FormatCliExit("cannot get alr user", err)
}
if _, err := os.Stat(sourceDir); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("cannot read %s", sourceDir), err)
}
if err := utils.EnuseIsPrivilegedGroupMember(); err != nil {
return err
}
// Before escalating the rights, we made sure that
// 1. user in wheel group
// 2. user can access sourceDir
if err := utils.EscalateToRootUid(); err != nil {
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(
"bindfs",
fmt.Sprintf("--map=%s/alr:@%s/@alr", u.Uid, u.Gid),
sourceDir,
targetDir,
)
bindfsCmd.Stderr = os.Stderr
if err := bindfsCmd.Run(); err != nil {
return cliutils.FormatCliExit("failed to strart bindfs", err)
}
fmt.Println(targetDir)
_, _ = bufio.NewReader(os.Stdin).ReadString('\n')
slog.Debug("start unmount", "dir", targetDir)
umountCmd := exec.Command("umount", targetDir)
umountCmd.Stderr = os.Stderr
if err := umountCmd.Run(); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("failed to unmount %s", targetDir), err)
}
if err := os.Remove(targetDir); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("error removing directory %s", targetDir), err)
}
return nil
},
}
}