core API

core

package

API reference for the core package.

F
function

RootCheck

Parameters

display
bool

Returns

bool
core/utils.go:16-24
func RootCheck(display bool) bool

{
	if os.Geteuid() != 0 {
		if display {
			fmt.Println("You must be root to run this command")
		}
		return false
	}
	return true
}
F
function

AskConfirmation

Parameters

s
string

Returns

bool
core/utils.go:26-34
func AskConfirmation(s string) bool

{
	var response string
	fmt.Print(s + " [y/N]: ")
	fmt.Scanln(&response)
	if response == "y" || response == "Y" {
		return true
	}
	return false
}
F
function

CopyToUserTemp

Parameters

path
string

Returns

string
error
core/utils.go:36-70
func CopyToUserTemp(path string) (string, error)

{
	userCacheDir, err := os.UserCacheDir()
	if err != nil {
		return "", err
	}

	cacheDir := filepath.Join(userCacheDir, "apx")
	if _, err := os.Stat(cacheDir); os.IsNotExist(err) {
		if err := os.MkdirAll(cacheDir, 0755); err != nil {
			return "", err
		}
	}

	fileName := filepath.Base(path)
	newPath := filepath.Join(cacheDir, fileName)

	pathContents, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer pathContents.Close()

	newPathContents, err := os.Create(newPath)
	if err != nil {
		return "", err
	}
	defer newPathContents.Close()

	_, err = newPathContents.ReadFrom(pathContents)
	if err != nil {
		return "", err
	}

	return newPath, nil
}
F
function

getPrettifiedDate

getPrettifiedDate returns a human readable date from a timestamp

Parameters

date
string

Returns

string
core/utils.go:73-87
func getPrettifiedDate(date string) string

{
	t, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", date)
	if err != nil {
		return date
	}

	// If the date is less than 24h ago, return the time since
	if t.After(time.Now().Add(-24 * time.Hour)) {
		duration := time.Since(t).Round(time.Hour)
		hours := int(duration.Hours())
		return fmt.Sprintf("%dh ago", hours)
	}

	return t.Format("02 Jan 2006 15:04:05")
}
F
function

CreateApxTable

Parameters

writer
core/utils.go:89-100
func CreateApxTable(writer io.Writer) *tablewriter.Table

{
	table := tablewriter.NewWriter(writer)
	table.SetColumnSeparator("┊")
	table.SetCenterSeparator("┼")
	table.SetRowSeparator("┄")
	table.SetHeaderLine(true)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
	table.SetRowLine(true)

	return table
}
F
function

CopyFile

Parameters

src
string
dst
string

Returns

error
core/utils.go:102-121
func CopyFile(src, dst string) error

{
	source, err := os.Open(src)
	if err != nil {
		return err
	}
	defer source.Close()

	destination, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer destination.Close()

	_, err = io.Copy(destination, source)
	if err != nil {
		return err
	}

	return nil
}
F
function

SelectYamlFile

Parameters

basePath
string
name
string

Returns

string
core/utils.go:123-137
func SelectYamlFile(basePath string, name string) string

{
	const (
		YML  string = ".yml"
		YAML string = ".yaml"
	)

	yamlFile := filepath.Join(basePath, fmt.Sprintf("%s%s", name, YAML))
	ymlFile := filepath.Join(basePath, fmt.Sprintf("%s%s", name, YML))

	if _, err := os.Stat(yamlFile); errors.Is(err, os.ErrNotExist) {
		return ymlFile
	}

	return yamlFile
}
S
struct

Apx

core/apx.go:10-12
type Apx struct

Methods

Returns

error
func (*Apx) EssentialChecks() error
{
	err := a.CheckContainerTools()
	if err != nil {
		fmt.Println(`One or more core components are not available. 
Please refer to our documentation at https://documentation.vanillaos.org/`)
		return err
	}

	err = a.CheckAndCreateUserStacksDirectory()
	if err != nil {
		fmt.Println(`Failed to create stacks directory.`)
		return err
	}

	err = a.CheckAndCreateApxStorageDirectory()
	if err != nil {
		fmt.Println(`Failed to create apx storage directory.`)
		return err
	}

	err = a.CheckAndCreateApxUserPkgManagersDirectory()
	if err != nil {
		fmt.Println(`Failed to create apx user pkg managers directory.`)
		return err
	}

	return nil
}

Returns

error
func (*Apx) CheckContainerTools() error
{
	err := settings.TestFile(a.Cnf.DistroboxPath)
	if err != nil {
		if os.IsNotExist(err) {
			return errors.New("distrobox is not installed")
		}
		return err
	}

	if _, err := settings.LookPath("docker"); err != nil {
		if _, err := settings.LookPath("podman"); err != nil {
			return errors.New("container engine (docker or podman) not found")
		}
	}

	return nil
}

Returns

error
func (*Apx) CheckAndCreateUserStacksDirectory() error
{
	_, err := os.Stat(a.Cnf.UserStacksPath)
	if err != nil {
		if os.IsNotExist(err) {
			err = os.MkdirAll(a.Cnf.UserStacksPath, 0755)
			if err != nil {
				return fmt.Errorf("failed to create stacks directory: %w", err)
			}
		} else {
			return fmt.Errorf("failed to check stacks directory: %w", err)
		}
	}

	return nil
}

Returns

error
func (*Apx) CheckAndCreateApxStorageDirectory() error
{
	_, err := os.Stat(a.Cnf.ApxStoragePath)
	if err != nil {
		if os.IsNotExist(err) {
			err = os.MkdirAll(a.Cnf.ApxStoragePath, 0755)
			if err != nil {
				return fmt.Errorf("failed to create apx storage directory: %w", err)
			}
		} else {
			return fmt.Errorf("failed to check apx storage directory: %w", err)
		}
	}

	return nil
}

Returns

error
func (*Apx) CheckAndCreateApxUserPkgManagersDirectory() error
{
	_, err := os.Stat(a.Cnf.UserPkgManagersPath)
	if err != nil {
		if os.IsNotExist(err) {
			err = os.MkdirAll(a.Cnf.UserPkgManagersPath, 0755)
			if err != nil {
				return fmt.Errorf("failed to create apx user pkg managers directory: %w", err)
			}
		} else {
			return fmt.Errorf("failed to check apx user pkg managers directory: %w", err)
		}
	}

	return nil
}

Fields

Name Type Description
Cnf *settings.Config
F
function

NewApx

Parameters

Returns

core/apx.go:14-27
func NewApx(cnf *settings.Config) *Apx

{
	apx = &Apx{
		Cnf: cnf,
	}

	err := apx.EssentialChecks()
	if err != nil {
		// localisation features aren't available at this stage, so this error can't be translated
		fmt.Println("ERROR: Unable to find apx configuration files")
		return nil
	}

	return apx
}
F
function

NewStandardApx

Returns

core/apx.go:29-46
func NewStandardApx() *Apx

{
	cnf, err := settings.GetApxDefaultConfig()
	if err != nil {
		panic(err)
	}

	apx = &Apx{
		Cnf: cnf,
	}

	err = apx.EssentialChecks()
	if err != nil {
		// localisation features aren't available at this stage, so this error can't be translated
		fmt.Println("ERROR: Unable to find apx configuration files")
		return nil
	}
	return apx
}
S
struct

dbox

core/dbox.go:24-28
type dbox struct

Methods

RunCommand
Method

Parameters

command string
args []string
engineFlags []string
useEngine bool
captureOutput bool
muteOutput bool
rootFull bool
detachedMode bool

Returns

[]byte
error
func (*dbox) RunCommand(command string, args []string, engineFlags []string, useEngine bool, captureOutput bool, muteOutput bool, rootFull bool, detachedMode bool) ([]byte, error)
{
	entrypoint := apx.Cnf.DistroboxPath
	if useEngine {
		entrypoint = d.EngineBinary
	}

	// we need to append engineFlags after command, otherwise they will be
	// ignored in commands like "enter"
	finalArgs := []string{command}

	// NOTE: for engine-specific commands, we need to use pkexec for rootfull
	//		 containers, since podman does not offer a dedicated flag for this.
	if rootFull && useEngine {
		entrypoint = "pkexec"
		finalArgs = []string{d.EngineBinary, command}
	}

	envVars := []string{"DBX_SUDO_PROGRAM=pkexec"}

	// NOTE: the custom storage is not being used since it prevent other
	//		 utilities, like VSCode, to access the container.
	if d.Engine == "podman" {
		envVars = append(envVars, "CONTAINER_STORAGE_DRIVER="+apx.Cnf.StorageDriver)
		// envVars = append(envVars, "XDG_DATA_HOME="+apx.Cnf.ApxStoragePath)
	} else if d.Engine == "docker" {
		envVars = append(envVars, "DOCKER_STORAGE_DRIVER="+apx.Cnf.StorageDriver)
		// envVars = append(envVars, "DOCKER_DATA_ROOT="+apx.Cnf.ApxStoragePath)
	}

	if settings.IsFlatpak() {
		finalArgs = append([]string{entrypoint}, finalArgs...)
		for i := range envVars {
			envVars[i] = "--env=" + envVars[i]
		}
		finalArgs = append(envVars, finalArgs...)
		finalArgs = append([]string{"--host"}, finalArgs...)
		entrypoint = "flatpak-spawn"
	}

	cmd := exec.Command(entrypoint, finalArgs...)

	if detachedMode {
		cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
	}

	if !captureOutput && !muteOutput {
		cmd.Stdout = os.Stdout
	}
	if !muteOutput {
		cmd.Stderr = os.Stderr
	}
	cmd.Stdin = os.Stdin

	if !settings.IsFlatpak() {
		cmd.Env = append(os.Environ(), envVars...)
	}

	if len(engineFlags) > 0 {
		cmd.Args = append(cmd.Args, "--additional-flags")
		cmd.Args = append(cmd.Args, strings.Join(engineFlags, " "))
	}

	// NOTE: the root flag is not being used by the Apx CLI, but it's useful
	//		 for those using Apx as a library, e.g. VSO.
	if rootFull && !useEngine {
		cmd.Args = append(cmd.Args, "--root")
	}

	cmd.Args = append(cmd.Args, args...)

	if os.Getenv("APX_VERBOSE") == "1" {
		fmt.Println("Running a command:")
		fmt.Println("\tCommand:", cmd.String())
		fmt.Println("\tcaptureOutput:", captureOutput)
		fmt.Println("\tmuteOutput:", muteOutput)
		fmt.Println("\trootFull:", rootFull)
		fmt.Println("\tdetachedMode:", detachedMode)
	}

	if detachedMode {
		err := cmd.Start()
		if err != nil {
			return nil, err
		}
		return nil, nil
	}

	if captureOutput {
		output, err := cmd.Output()
		if err != nil {
			if exitErr, ok := err.(*exec.ExitError); ok {
				return output, errors.New(string(exitErr.Stderr))
			}
		}
		return output, err
	}

	err := cmd.Run()
	return nil, err
}

Parameters

rootFull bool

Returns

error
func (*dbox) ListContainers(rootFull bool) ([]dboxContainer, error)
{
	output, err := d.RunCommand("ps", []string{
		"-a",
		"--format", "{{.ID}}|{{.CreatedAt}}|{{.Status}}|{{.Labels}}|{{.Names}}",
	}, []string{}, true, true, false, rootFull, false)
	if err != nil {
		return nil, err
	}

	rows := strings.Split(string(output), "\n")
	containers := []dboxContainer{}

	for _, row := range rows {
		if row == "" {
			continue
		}

		rowItems := strings.Split(row, "|")
		if len(rowItems) != 5 {
			continue
		}

		container := dboxContainer{
			ID:        strings.Trim(rowItems[0], "\""),
			CreatedAt: strings.Trim(rowItems[1], "\""),
			Status:    strings.Trim(rowItems[2], "\""),
			Name:      strings.Trim(rowItems[4], "\""),
			Labels:    map[string]string{},
		}

		// example labels: map[manager:apx name:alpine stack:alpine]
		labels := strings.ReplaceAll(rowItems[3], "map[", "")
		labels = strings.ReplaceAll(labels, "]", "")
		labelsItems := strings.Split(labels, " ")
		for _, label := range labelsItems {
			labelItems := strings.Split(label, ":")
			if len(labelItems) != 2 {
				continue
			}

			container.Labels[labelItems[0]] = labelItems[1]
		}

		containers = append(containers, container)
	}

	return containers, nil
}
GetContainer
Method

Parameters

name string
rootFull bool

Returns

error
func (*dbox) GetContainer(name string, rootFull bool) (*dboxContainer, error)
{
	containers, err := d.ListContainers(rootFull)
	if err != nil {
		return nil, err
	}

	for _, container := range containers {
		// fmt.Println("found container", container.Name, "requested", name)
		if container.Name == name {
			return &container, nil
		}
	}

	return nil, errors.New("container not found")
}

Parameters

name string
rootFull bool

Returns

error
func (*dbox) ContainerDelete(name string, rootFull bool) error
{
	_, err := d.RunCommand("rm", []string{
		"--force",
		name,
	}, []string{}, false, false, true, rootFull, false)
	return err
}

Parameters

name string
image string
additionalPackages []string
home string
labels map[string]string
withInit bool
rootFull bool
unshared bool
withNvidiaIntegration bool
hostname string

Returns

error
func (*dbox) CreateContainer(name string, image string, additionalPackages []string, home string, labels map[string]string, withInit bool, rootFull bool, unshared bool, withNvidiaIntegration bool, hostname string) error
{
	args := []string{
		"--image", image,
		"--name", name,
		"--no-entry",
		"--yes",
		"--pull",
	}

	if home != "" {
		args = append(args, "--home", home)
	}

	if hasNvidiaGPU() && withNvidiaIntegration {
		args = append(args, "--nvidia")
	}

	if withInit {
		args = append(args, "--init")
	}

	if unshared {
		args = append(args, "--unshare-all")
	}

	if hostname != "" {
		args = append(args, "--hostname", hostname)
	}

	if len(additionalPackages) > 0 {
		args = append(args, "--additional-packages")
		args = append(args, strings.Join(additionalPackages, " "))
	}

	engineFlags := []string{}
	for key, value := range labels {
		engineFlags = append(engineFlags, fmt.Sprintf("--label=%s=%s", key, value))
	}
	engineFlags = append(engineFlags, "--label=manager=apx")

	_, err := d.RunCommand("create", args, engineFlags, false, false, false, rootFull, false)
	// fmt.Println(string(out))
	return err
}

Parameters

name string
command []string
rootFull bool
detachedMode bool

Returns

error
func (*dbox) RunContainerCommand(name string, command []string, rootFull, detachedMode bool) error
{
	args := []string{
		"--name", name,
		"--",
	}

	args = append(args, command...)

	_, err := d.RunCommand("run", args, []string{}, false, false, false, rootFull, detachedMode)
	return err
}
ContainerExec
Method

Parameters

name string
captureOutput bool
muteOutput bool
rootFull bool
detachedMode bool
args ...string

Returns

string
error
func (*dbox) ContainerExec(name string, captureOutput bool, muteOutput bool, rootFull, detachedMode bool, args ...string) (string, error)
{
	finalArgs := []string{
		// "--verbose",
		name,
		"--",
	}

	finalArgs = append(finalArgs, args...)
	engineFlags := []string{}

	out, err := d.RunCommand("enter", finalArgs, engineFlags, false, captureOutput, muteOutput, rootFull, detachedMode)
	// if error 130, it means that the user pressed CTRL+D, so ignore
	if err != nil && err.Error() == "exit status 130" {
		return string(out), nil
	}

	return string(out), err
}

Parameters

name string
rootFull bool

Returns

error
func (*dbox) ContainerEnter(name string, rootFull bool) error
{
	finalArgs := []string{
		name,
	}

	engineFlags := []string{}

	_, err := d.RunCommand("enter", finalArgs, engineFlags, false, false, false, rootFull, false)
	// if error 130, it means that the user pressed CTRL+D, so ignore
	if err != nil && err.Error() == "exit status 130" {
		return nil
	}

	return err
}

Parameters

name string
rootFull bool

Returns

error
func (*dbox) ContainerStart(name string, rootFull bool) error
{
	_, err := d.RunCommand("start", []string{
		name,
	}, []string{}, true, false, false, rootFull, false)
	return err
}
ContainerStop
Method

Parameters

name string
rootFull bool

Returns

error
func (*dbox) ContainerStop(name string, rootFull bool) error
{
	finalArgs := []string{
		name,
		"--yes",
	}

	engineFlags := []string{}

	_, err := d.RunCommand("stop", finalArgs, engineFlags, false, false, false, rootFull, false)
	return err
}

Parameters

name string
delete bool
rootFull bool
args ...string

Returns

error
func (*dbox) ContainerExport(name string, delete bool, rootFull bool, args ...string) error
{
	finalArgs := []string{"distrobox-export"}

	if delete {
		finalArgs = append(finalArgs, "--delete")
	}

	finalArgs = append(finalArgs, args...)

	_, err := d.ContainerExec(name, true, true, rootFull, false, finalArgs...)
	return err
}

Parameters

containerName string
appName string
label string
rootFull bool

Returns

error
func (*dbox) ContainerExportDesktopEntry(containerName string, appName string, label string, rootFull bool) error
{
	args := []string{"--app", appName, "--export-label", label}
	return d.ContainerExport(containerName, false, rootFull, args...)
}

Parameters

containerName string
appName string
rootFull bool

Returns

error
func (*dbox) ContainerUnexportDesktopEntry(containerName string, appName string, rootFull bool) error
{
	args := []string{"--app", appName}
	return d.ContainerExport(containerName, true, rootFull, args...)
}

Parameters

containerName string
binary string
exportPath string
rootFull bool

Returns

error
func (*dbox) ContainerExportBin(containerName string, binary string, exportPath string, rootFull bool) error
{
	args := []string{"--bin", binary, "--export-path", exportPath}
	return d.ContainerExport(containerName, false, rootFull, args...)
}

Parameters

containerName string
binary string
rootFull bool

Returns

error
func (*dbox) ContainerUnexportBin(containerName string, binary string, rootFull bool) error
{
	args := []string{"--bin", binary}
	return d.ContainerExport(containerName, true, rootFull, args...)
}

Fields

Name Type Description
Engine string
EngineBinary string
Version string
S
struct

dboxContainer

core/dbox.go:30-36
type dboxContainer struct

Fields

Name Type Description
ID string
CreatedAt string
Status string
Labels map[string]string
Name string
F
function

NewDbox

Returns

error
core/dbox.go:38-51
func NewDbox() (*dbox, error)

{
	engineBinary, engine := getEngine()

	version, err := dboxGetVersion()
	if err != nil {
		return nil, err
	}

	return &dbox{
		Engine:       engine,
		EngineBinary: engineBinary,
		Version:      version,
	}, nil
}
F
function

getEngine

Returns

string
string
core/dbox.go:53-66
func getEngine() (string, string)

{
	podmanBinary, err := settings.LookPath("podman")
	if err == nil {
		return podmanBinary, "podman"
	}

	dockerBinary, err := settings.LookPath("docker")
	if err == nil {
		return dockerBinary, "docker"
	}

	log.Fatal("no container engine found. Please install Podman or Docker.")
	return "", ""
}
F
function

dboxGetVersion

Returns

version
string
err
error
core/dbox.go:68-86
func dboxGetVersion() (version string, err error)

{
	entrypoint := apx.Cnf.DistroboxPath
	finalArgs := []string{"version"}
	if settings.IsFlatpak() {
		finalArgs = append([]string{"--host", entrypoint}, finalArgs...)
		entrypoint = "flatpak-spawn"
	}
	output, err := exec.Command(entrypoint, finalArgs...).Output()
	if err != nil {
		return "", err
	}

	splitted := strings.Split(string(output), "distrobox: ")
	if len(splitted) != 2 {
		return "", errors.New("can't retrieve distrobox version")
	}

	return splitted[1], nil
}
F
function

IsOverlayTypeFS

Returns

bool
core/essentials.go:70-77
func IsOverlayTypeFS() bool

{
	out, err := exec.Command("df", "-T", "/").Output()
	if err != nil {
		return false
	}

	return strings.Contains(string(out), "overlay")
}
F
function

ExitIfOverlayTypeFS

core/essentials.go:79-84
func ExitIfOverlayTypeFS()

{
	if IsOverlayTypeFS() {
		log.Default().Printf("Apx does not work with overlay type filesystem.")
		os.Exit(1)
	}
}
F
function

hasNvidiaGPU

Returns

bool
core/essentials.go:134-137
func hasNvidiaGPU() bool

{
	_, err := os.Stat("/dev/nvidia0")
	return err == nil
}
S
struct

PkgManager

PkgManager represents a package manager in Apx, a set of instructions to handle a package manager.

core/pkgManager.go:24-49
type PkgManager struct

Methods

Save
Method

Save saves the package manager to the specified path.

Returns

error
func (*PkgManager) Save() error
{
	data, err := yaml.Marshal(pkgManager)
	if err != nil {
		return err
	}

	filePath := SelectYamlFile(apx.Cnf.UserPkgManagersPath, pkgManager.Name)
	err = os.WriteFile(filePath, data, 0644)
	return err
}
Remove
Method

Remove removes the package manager from the specified path.

Returns

error
func (*PkgManager) Remove() error
{
	if pkgManager.BuiltIn {
		return errors.New("cannot remove built-in package manager")
	}

	filePath := SelectYamlFile(apx.Cnf.UserPkgManagersPath, pkgManager.Name)
	err := os.Remove(filePath)
	return err
}
GenCmd
Method

GenCmd generates the command to run inside the container.

Parameters

cmd string
args ...string

Returns

[]string
func (*PkgManager) GenCmd(cmd string, args ...string) []string
{
	finalArgs := make([]string, 0)

	if pkgManager.NeedSudo {
		finalArgs = append(finalArgs, "sudo")
	}

	if pkgManager.Model == 0 || pkgManager.Model == 1 {
		// no-translate (DEPRECATION WARNING)
		fmt.Println("!!! DEPRECATION WARNING: Model 1 will be removed in the future, please update your Apx package manager to use model 2.")
		finalArgs = append(finalArgs, pkgManager.Name)
		finalArgs = append(finalArgs, cmd)
		finalArgs = append(finalArgs, args...)
	} else {
		cmdItems := strings.Fields(cmd)
		finalArgs = append(finalArgs, cmdItems...)
		finalArgs = append(finalArgs, args...)
	}

	return finalArgs
}
Export
Method

Export exports the package manager to the specified path.

Parameters

path string

Returns

error
func (*PkgManager) Export(path string) error
{
	if _, err := os.Stat(path); os.IsNotExist(err) {
		err = os.MkdirAll(path, 0755)
		if err != nil {
			return err
		}
	}

	filePath := SelectYamlFile(path, pkgManager.Name)
	data, err := yaml.Marshal(pkgManager)
	if err != nil {
		return err
	}

	err = os.WriteFile(filePath, data, 0644)
	if err != nil {
		return err
	}

	return nil
}

Fields

Name Type Description
Model int
Name string
NeedSudo bool
CmdAutoRemove string
CmdClean string
CmdInstall string
CmdList string
CmdPurge string
CmdRemove string
CmdSearch string
CmdShow string
CmdUpdate string
CmdUpgrade string
BuiltIn bool
F
function

NewPkgManager

NewPkgManager creates a new PkgManager instance.

Parameters

name
string
needSudo
bool
autoRemove
string
clean
string
install
string
list
string
purge
string
remove
string
search
string
show
string
update
string
upgrade
string
builtIn
bool

Returns

core/pkgManager.go:52-69
func NewPkgManager(name string, needSudo bool, autoRemove, clean, install, list, purge, remove, search, show, update, upgrade string, builtIn bool) *PkgManager

{
	return &PkgManager{
		Name:          name,
		NeedSudo:      needSudo,
		CmdAutoRemove: autoRemove,
		CmdClean:      clean,
		CmdInstall:    install,
		CmdList:       list,
		CmdPurge:      purge,
		CmdRemove:     remove,
		CmdSearch:     search,
		CmdShow:       show,
		CmdUpdate:     update,
		CmdUpgrade:    upgrade,
		BuiltIn:       builtIn,
		Model:         2,
	}
}
F
function

LoadPkgManager

LoadPkgManager loads a package manager from the specified path.

Parameters

name
string

Returns

error
core/pkgManager.go:72-80
func LoadPkgManager(name string) (*PkgManager, error)

{
	userPkgFile := SelectYamlFile(apx.Cnf.UserPkgManagersPath, name)
	pkgManager, err := loadPkgManagerFromPath(userPkgFile)
	if err != nil {
		pkgFile := SelectYamlFile(apx.Cnf.PkgManagersPath, name)
		pkgManager, err = loadPkgManagerFromPath(pkgFile)
	}
	return pkgManager, err
}
F
function

loadPkgManagerFromPath

loadPkgManagerFromPath loads a package manager from the specified path.

Parameters

path
string

Returns

error
core/pkgManager.go:83-102
func loadPkgManagerFromPath(path string) (*PkgManager, error)

{
	pkgManager := &PkgManager{}

	f, err := os.Open(path)
	if err != nil {
		return nil, errors.New("package manager not found")
	}

	data, err := io.ReadAll(f)
	if err != nil {
		return nil, err
	}

	err = yaml.Unmarshal(data, pkgManager)
	if err != nil {
		return nil, err
	}

	return pkgManager, nil
}
F
function

ListPkgManagers

ListPkgManagers lists all the package managers.

Returns

core/pkgManager.go:151-166
func ListPkgManagers() []*PkgManager

{
	pkgManagers := make([]*PkgManager, 0)

	pkgManagersFromEtc := listPkgManagersFromPath(apx.Cnf.UserPkgManagersPath)
	pkgManagers = append(pkgManagers, pkgManagersFromEtc...)

	if apx.Cnf.PkgManagersPath == apx.Cnf.UserPkgManagersPath {
		// user install
		return pkgManagers
	}

	pkgManagersFromShare := listPkgManagersFromPath(apx.Cnf.PkgManagersPath)
	pkgManagers = append(pkgManagers, pkgManagersFromShare...)

	return pkgManagers
}
F
function

listPkgManagersFromPath

listPkgManagersFromPath lists all the package managers from the specified path.

Parameters

path
string

Returns

core/pkgManager.go:169-191
func listPkgManagersFromPath(path string) []*PkgManager

{
	pkgManagers := make([]*PkgManager, 0)

	files, err := os.ReadDir(path)
	if err != nil {
		return pkgManagers
	}

	for _, file := range files {
		extension := filepath.Ext(file.Name())

		if !file.IsDir() && (extension == ".yaml" || extension == ".yml") {
			// Remove the ".yaml" or ".yml" extension
			pkgManagerName := file.Name()[:(len(file.Name()) - len(extension))]
			pkgManager, err := LoadPkgManager(pkgManagerName)
			if err == nil {
				pkgManagers = append(pkgManagers, pkgManager)
			}
		}
	}

	return pkgManagers
}
F
function

PkgManagerExists

PkgManagerExists checks if the package manager exists.

Parameters

name
string

Returns

bool
core/pkgManager.go:194-197
func PkgManagerExists(name string) bool

{
	_, err := LoadPkgManager(name)
	return err == nil
}
F
function

LoadPkgManagerFromPath

LoadPkgManagerFromPath loads a package manager from the specified path.

Parameters

path
string

Returns

error
core/pkgManager.go:200-223
func LoadPkgManagerFromPath(path string) (*PkgManager, error)

{
	pkgManager := &PkgManager{}

	f, err := os.Open(path)
	if err != nil {
		return nil, errors.New("package manager not found")
	}

	data, err := io.ReadAll(f)
	if err != nil {
		return nil, err
	}

	err = yaml.Unmarshal(data, pkgManager)
	if err != nil {
		return nil, err
	}

	if pkgManager.Model == 0 {
		pkgManager.Model = 1 // assuming old model if not specified
	}

	return pkgManager, nil
}
S
struct

Stack

Stack represents a stack in Apx, a set of instructions to build a container.

core/stack.go:22-28
type Stack struct

Methods

Save
Method

Save saves the stack to a YAML file.

Returns

error
func (*Stack) Save() error
{
	data, err := yaml.Marshal(stack)
	if err != nil {
		return err
	}

	filePath := SelectYamlFile(apx.Cnf.UserStacksPath, stack.Name)
	err = os.WriteFile(filePath, data, 0644)
	return err
}
GetPkgManager
Method

GetPkgManager returns the package manager of the stack.

Returns

error
func (*Stack) GetPkgManager() (*PkgManager, error)
{
	pkgManager, err := LoadPkgManager(stack.PkgManager)
	if err != nil {
		return nil, err
	}

	return pkgManager, nil
}
Remove
Method

Remove removes the stack file.

Returns

error
func (*Stack) Remove() error
{
	if stack.BuiltIn {
		return errors.New("cannot remove built-in stack")
	}

	filePath := SelectYamlFile(apx.Cnf.UserStacksPath, stack.Name)
	err := os.Remove(filePath)
	return err
}
Export
Method

Export exports the stack YAML to the specified path.

Parameters

path string

Returns

error
func (*Stack) Export(path string) error
{
	if _, err := os.Stat(path); os.IsNotExist(err) {
		err = os.MkdirAll(path, 0755)
		if err != nil {
			return err
		}
	}

	filePath := SelectYamlFile(path, stack.Name)
	data, err := yaml.Marshal(stack)
	if err != nil {
		return err
	}

	err = os.WriteFile(filePath, data, 0644)
	if err != nil {
		return err
	}

	return nil
}

Fields

Name Type Description
Name string
Base string
Packages []string
PkgManager string
BuiltIn bool
F
function

NewStack

NewStack creates a new Stack instance.

Parameters

name
string
base
string
packages
[]string
pkgManager
string
builtIn
bool

Returns

core/stack.go:31-39
func NewStack(name, base string, packages []string, pkgManager string, builtIn bool) *Stack

{
	return &Stack{
		Name:       name,
		Base:       base,
		Packages:   packages,
		PkgManager: pkgManager,
		BuiltIn:    builtIn,
	}
}
F
function

LoadStack

LoadStack loads a stack from the specified path.

Parameters

name
string

Returns

error
core/stack.go:42-50
func LoadStack(name string) (*Stack, error)

{
	usrStackFile := SelectYamlFile(apx.Cnf.UserStacksPath, name)
	stack, err := LoadStackFromPath(usrStackFile)
	if err != nil {
		stackFile := SelectYamlFile(apx.Cnf.StacksPath, name)
		stack, err = LoadStackFromPath(stackFile)
	}
	return stack, err
}
F
function

LoadStackFromPath

LoadStackFromPath loads a stack from the specified path.

Parameters

path
string

Returns

error
core/stack.go:53-76
func LoadStackFromPath(path string) (*Stack, error)

{
	stack := &Stack{}

	f, err := os.Open(path)
	if err != nil {
		return nil, errors.New("stack not found")
	}

	data, err := io.ReadAll(f)
	if err != nil {
		return nil, err
	}

	err = yaml.Unmarshal(data, stack)
	if err != nil {
		return nil, err
	}

	if stack.Name == "" || stack.Base == "" || stack.PkgManager == "" {
		return nil, errors.New("invalid stack file")
	}

	return stack, nil
}
F
function

ListStacks

ListStacks returns a list of all stacks.

Returns

core/stack.go:135-150
func ListStacks() []*Stack

{
	stacks := make([]*Stack, 0)

	stacksFromEtc := listStacksFromPath(apx.Cnf.UserStacksPath)
	stacks = append(stacks, stacksFromEtc...)

	if apx.Cnf.UserStacksPath == apx.Cnf.StacksPath {
		// user install
		return stacks
	}

	stacksFromShare := listStacksFromPath(apx.Cnf.StacksPath)
	stacks = append(stacks, stacksFromShare...)

	return stacks
}
F
function

ListStackForPkgManager

ListStackForPkgManager returns a list of stacks for the specified package manager.

Parameters

pkgManager
string

Returns

core/stack.go:153-171
func ListStackForPkgManager(pkgManager string) []*Stack

{
	stacks := make([]*Stack, 0)

	stacksFromEtc := listStacksFromPath(apx.Cnf.UserStacksPath)
	for _, stack := range stacksFromEtc {
		if stack.PkgManager == pkgManager {
			stacks = append(stacks, stack)
		}
	}

	stacksFromShare := listStacksFromPath(apx.Cnf.StacksPath)
	for _, stack := range stacksFromShare {
		if stack.PkgManager == pkgManager {
			stacks = append(stacks, stack)
		}
	}

	return stacks
}
F
function

listStacksFromPath

listStacksFromPath returns a list of stacks from the specified path.
this func does not return an error, since Apx is meant to be portable and
the main directory can be missing, while the user directory is always created.

Parameters

path
string

Returns

core/stack.go:176-198
func listStacksFromPath(path string) []*Stack

{
	stacks := make([]*Stack, 0)

	files, err := os.ReadDir(path)
	if err != nil {
		return stacks
	}

	for _, file := range files {
		extension := filepath.Ext(file.Name())

		if !file.IsDir() && (extension == ".yaml" || extension == ".yml") {
			// Remove the ".yaml" or ".yml" extension
			stackName := file.Name()[:(len(file.Name()) - len(extension))]
			stack, err := LoadStack(stackName)
			if err == nil {
				stacks = append(stacks, stack)
			}
		}
	}

	return stacks
}
F
function

StackExists

StackExists checks if a stack exists.

Parameters

name
string

Returns

bool
core/stack.go:201-204
func StackExists(name string) bool

{
	s, _ := LoadStack(name)
	return s != nil
}
S
struct

SubSystem

core/subSystem.go:24-37
type SubSystem struct

Methods

Create
Method

Returns

error
func (*SubSystem) Create() error
{
	dbox, err := NewDbox()
	if err != nil {
		return err
	}

	labels := map[string]string{
		"stack": strings.ReplaceAll(s.Stack.Name, " ", "\\ "),
		"name":  strings.ReplaceAll(s.Name, " ", "\\ "),
	}

	if s.IsManaged {
		labels["managed"] = "true"
	}

	if s.HasInit {
		labels["hasInit"] = "true"
	}

	if s.IsUnshared {
		labels["unshared"] = "true"
	}

	if s.HasNvidiaIntegration {
		labels["nvidia"] = "true"
	}

	err = dbox.CreateContainer(
		s.InternalName,
		s.Stack.Base,
		s.Stack.Packages,
		s.Home,
		labels,
		s.HasInit,
		s.IsRootfull,
		s.IsUnshared,
		s.HasNvidiaIntegration,
		s.Hostname,
	)
	if err != nil {
		return err
	}

	return nil
}
Exec
Method

Parameters

captureOutput bool
detachedMode bool
args ...string

Returns

string
error
func (*SubSystem) Exec(captureOutput, detachedMode bool, args ...string) (string, error)
{
	dbox, err := NewDbox()
	if err != nil {
		return "", err
	}

	out, err := dbox.ContainerExec(s.InternalName, captureOutput, false, s.IsRootfull, detachedMode, args...)
	if err != nil {
		return "", err
	}

	if captureOutput {
		return out, nil
	}

	return "", nil
}
Enter
Method

Returns

error
func (*SubSystem) Enter() error
{
	dbox, err := NewDbox()
	if err != nil {
		return err
	}
	return dbox.ContainerEnter(s.InternalName, s.IsRootfull)
}
Start
Method

Returns

error
func (*SubSystem) Start() error
{
	dbox, err := NewDbox()
	if err != nil {
		return err
	}
	return dbox.ContainerStart(s.InternalName, s.IsRootfull)
}
Stop
Method

Returns

error
func (*SubSystem) Stop() error
{
	dbox, err := NewDbox()
	if err != nil {
		return err
	}
	return dbox.ContainerStop(s.InternalName, s.IsRootfull)
}
Remove
Method

Returns

error
func (*SubSystem) Remove() error
{
	dbox, err := NewDbox()
	if err != nil {
		return err
	}

	return dbox.ContainerDelete(s.InternalName, s.IsRootfull)
}
Reset
Method

Returns

error
func (*SubSystem) Reset() error
{
	err := s.Remove()
	if err != nil {
		return err
	}

	return s.Create()
}

Parameters

appName string

Returns

error
func (*SubSystem) ExportDesktopEntry(appName string) error
{
	dbox, err := NewDbox()
	if err != nil {
		return err
	}

	return dbox.ContainerExportDesktopEntry(s.InternalName, appName, fmt.Sprintf("on %s", s.Name), s.IsRootfull)
}

Parameters

args ...string

Returns

int
error
func (*SubSystem) ExportDesktopEntries(args ...string) (int, error)
{
	exportedN := 0

	for _, appName := range args {
		err := s.ExportDesktopEntry(appName)
		if err != nil {
			return exportedN, err
		}

		exportedN++
	}

	return exportedN, nil
}

Parameters

args ...string

Returns

int
error
func (*SubSystem) UnexportDesktopEntries(args ...string) (int, error)
{
	exportedN := 0

	for _, appName := range args {
		err := s.UnexportDesktopEntry(appName)
		if err != nil {
			return exportedN, err
		}

		exportedN++
	}

	return exportedN, nil
}
ExportBin
Method

Parameters

binary string
exportPath string

Returns

error
func (*SubSystem) ExportBin(binary string, exportPath string) error
{
	if !strings.HasPrefix(binary, "/") {
		binaryPath, err := s.Exec(true, false, "which", binary)
		if err != nil {
			return err
		}

		binary = strings.TrimSpace(binaryPath)
	}

	binaryName := filepath.Base(binary)

	dbox, err := NewDbox()
	if err != nil {
		return err
	}

	homeDir, err := os.UserHomeDir()
	if err != nil {
		return err
	}

	if exportPath == "" {
		exportPath = filepath.Join(homeDir, ".local", "bin")
	}

	joinedPath := filepath.Join(exportPath, binaryName)
	if _, err := os.Stat(joinedPath); err == nil {
		tmpExportPath := fmt.Sprintf("/tmp/%s", uuid.New().String())
		err = os.MkdirAll(tmpExportPath, 0o755)
		if err != nil {
			return err
		}

		err = dbox.ContainerExportBin(s.InternalName, binary, tmpExportPath, s.IsRootfull)
		if err != nil {
			return err
		}

		err = CopyFile(filepath.Join(tmpExportPath, binaryName), filepath.Join(exportPath, fmt.Sprintf("%s-%s", binaryName, s.InternalName)))
		if err != nil {
			return err
		}

		err = os.RemoveAll(tmpExportPath)
		if err != nil {
			return err
		}

		err = os.Chmod(filepath.Join(exportPath, fmt.Sprintf("%s-%s", binaryName, s.InternalName)), 0o755)
		if err != nil {
			return err
		}

		return nil
	}

	err = os.MkdirAll(exportPath, 0o755)
	if err != nil {
		return err
	}

	err = dbox.ContainerExportBin(s.InternalName, binary, exportPath, s.IsRootfull)
	if err != nil {
		return err
	}

	return nil
}

Parameters

appName string

Returns

error
func (*SubSystem) UnexportDesktopEntry(appName string) error
{
	dbox, err := NewDbox()
	if err != nil {
		return err
	}

	return dbox.ContainerUnexportDesktopEntry(s.InternalName, appName, s.IsRootfull)
}
UnexportBin
Method

Parameters

binary string
exportPath string

Returns

error
func (*SubSystem) UnexportBin(binary string, exportPath string) error
{
	if !strings.HasPrefix(binary, "/") {
		binaryPath, err := s.Exec(true, false, "which", binary)
		if err != nil {
			return err
		}

		binary = strings.TrimSpace(binaryPath)
	}

	dbox, err := NewDbox()
	if err != nil {
		return err
	}

	return dbox.ContainerUnexportBin(s.InternalName, binary, s.IsRootfull)
}

Fields

Name Type Description
InternalName string
Name string
Stack *Stack
Home string
Status string
ExportedPrograms map[string]map[string]string
HasInit bool
IsManaged bool
IsRootfull bool
IsUnshared bool
HasNvidiaIntegration bool
Hostname string
F
function

NewSubSystem

Parameters

name
string
stack
home
string
hasInit
bool
isManaged
bool
isRootfull
bool
isUnshared
bool
hasNvidiaIntegration
bool
hostname
string

Returns

error
core/subSystem.go:39-53
func NewSubSystem(name string, stack *Stack, home string, hasInit bool, isManaged bool, isRootfull bool, isUnshared bool, hasNvidiaIntegration bool, hostname string) (*SubSystem, error)

{
	internalName := genInternalName(name)
	return &SubSystem{
		InternalName:         internalName,
		Name:                 name,
		Stack:                stack,
		Home:                 home,
		HasInit:              hasInit,
		IsManaged:            isManaged,
		IsRootfull:           isRootfull,
		IsUnshared:           isUnshared,
		HasNvidiaIntegration: hasNvidiaIntegration,
		Hostname:             hostname,
	}, nil
}
F
function

genInternalName

Parameters

name
string

Returns

string
core/subSystem.go:55-57
func genInternalName(name string) string

{
	return fmt.Sprintf("apx-%s", strings.ReplaceAll(strings.ToLower(name), " ", "-"))
}
F
function

findExportedBinaries

Parameters

internalName
string

Returns

map[string]map[string]string
core/subSystem.go:59-112
func findExportedBinaries(internalName string) map[string]map[string]string

{
	home, err := os.UserHomeDir()
	if err != nil {
		return map[string]map[string]string{}
	}
	binPath := filepath.Join(home, ".local", "bin")
	if _, err := os.Stat(binPath); os.IsNotExist(err) {
		return map[string]map[string]string{}
	}
	binDir := os.DirFS(binPath)

	binaries := map[string]map[string]string{}
	err = fs.WalkDir(binDir, ".", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		}

		file, err := os.Open(filepath.Join(binPath, path))
		if err != nil {
			return err
		}
		defer file.Close()

		var header = "#!/bin/sh\n# distrobox_binary\n# name: " + internalName
		var maxTokenSize = len(header)
		var buffer = make([]byte, maxTokenSize)

		n, err := file.Read(buffer)
		if err != nil && err != io.EOF {
			return err
		}

		buffer = buffer[:n]

		if string(buffer) == header {
			name := filepath.Base(path)
			binaries[name] = map[string]string{
				"Exec": path,
				"Name": name,
			}
		}

		return nil
	})
	if err != nil {
		fmt.Printf("error reading binaries: %s\n", err)
		return map[string]map[string]string{}
	}

	return binaries
}
F
function

findExportedPrograms

Parameters

internalName
string
name
string

Returns

map[string]map[string]string
core/subSystem.go:114-172
func findExportedPrograms(internalName string, name string) map[string]map[string]string

{
	home, err := os.UserHomeDir()
	if err != nil {
		return map[string]map[string]string{}
	}

	files, err := filepath.Glob(fmt.Sprintf("%s/.local/share/applications/%s-*.desktop", home, internalName))
	if err != nil {
		return map[string]map[string]string{}
	}

	programs := map[string]map[string]string{}
	for _, file := range files {
		f, err := os.Open(file)
		if err != nil {
			return map[string]map[string]string{}
		}
		defer f.Close()

		data, err := io.ReadAll(f)
		if err != nil {
			continue
		}

		pName := ""
		pExec := ""
		pIcon := ""
		pGenericName := ""
		for _, line := range strings.Split(string(data), "\n") {
			if strings.HasPrefix(line, "Name=") {
				pName = strings.TrimPrefix(line, "Name=")
				pName = strings.ReplaceAll(pName, fmt.Sprintf(" on %s", name), "")
			}

			if strings.HasPrefix(line, "Exec=") {
				pExec = strings.TrimPrefix(line, "Exec=")
			}

			if strings.HasPrefix(line, "Icon=") {
				pIcon = strings.TrimPrefix(line, "Icon=")
			}

			if strings.HasPrefix(line, "GenericName=") {
				pGenericName = strings.TrimPrefix(line, "GenericName=")
			}
		}

		if pName != "" && pExec != "" {
			programs[pName] = map[string]string{
				"Exec":        pExec,
				"Icon":        pIcon,
				"Name":        pName,
				"GenericName": pGenericName,
			}
		}
	}

	return programs
}
F
function

findExported

Parameters

internalName
string
name
string

Returns

map[string]map[string]string
core/subSystem.go:174-184
func findExported(internalName string, name string) map[string]map[string]string

{
	bins := findExportedBinaries(internalName)
	progs := findExportedPrograms(internalName, name)

	// If duplicate is found, give priority to application
	for k, v := range progs {
		bins[k] = v
	}

	return bins
}
F
function

LoadSubSystem

Parameters

name
string
isRootFull
bool

Returns

error
core/subSystem.go:232-258
func LoadSubSystem(name string, isRootFull bool) (*SubSystem, error)

{
	dbox, err := NewDbox()
	if err != nil {
		return nil, err
	}

	internalName := genInternalName(name)
	container, err := dbox.GetContainer(internalName, isRootFull)
	if err != nil {
		return nil, err
	}

	stack, err := LoadStack(container.Labels["stack"])
	if err != nil {
		return nil, err
	}
	return &SubSystem{
		InternalName: internalName,
		Name:         container.Labels["name"],
		Stack:        stack,
		Status:       container.Status,
		HasInit:      container.Labels["hasInit"] == "true",
		IsManaged:    container.Labels["managed"] == "true",
		IsRootfull:   isRootFull,
		IsUnshared:   container.Labels["unshared"] == "true",
	}, nil
}
F
function

ListSubSystems

Parameters

includeManaged
bool
includeRootFull
bool

Returns

error
core/subSystem.go:260-303
func ListSubSystems(includeManaged bool, includeRootFull bool) ([]*SubSystem, error)

{
	dbox, err := NewDbox()
	if err != nil {
		return nil, err
	}

	containers, err := dbox.ListContainers(includeRootFull)
	if err != nil {
		return nil, err
	}

	subsystems := []*SubSystem{}
	for _, container := range containers {
		if _, ok := container.Labels["name"]; !ok {
			// log.Printf("Container %s has no name label, skipping", container.Name)
			continue
		}

		if !includeManaged {
			if _, ok := container.Labels["managed"]; ok {
				continue
			}
		}

		stack, err := LoadStack(container.Labels["stack"])
		if err != nil {
			log.Printf("Error loading stack %s: %s", container.Labels["stack"], err)
			continue
		}

		internalName := genInternalName(container.Labels["name"])
		subsystem := &SubSystem{
			InternalName:     internalName,
			Name:             container.Labels["name"],
			Stack:            stack,
			Status:           container.Status,
			ExportedPrograms: findExported(internalName, container.Labels["name"]),
		}

		subsystems = append(subsystems, subsystem)
	}

	return subsystems, nil
}
F
function

ListSubsystemForStack

ListSubsystemForStack returns a list of subsystems for the specified stack.

Parameters

stackName
string

Returns

error
core/subSystem.go:306-353
func ListSubsystemForStack(stackName string) ([]*SubSystem, error)

{
	dbox, err := NewDbox()
	if err != nil {
		return nil, err
	}

	containers, err := dbox.ListContainers(false)
	if err != nil {
		return nil, err
	}

	subsystems := []*SubSystem{}
	for _, container := range containers {
		containerManager, ok := container.Labels["manager"]
		if !ok || containerManager != "apx" {
			continue
		}

		containerName, ok := container.Labels["name"]
		if !ok {
			continue
		}

		containerStack, ok := container.Labels["stack"]
		if !ok || containerStack != stackName {
			continue
		}

		stack, err := LoadStack(containerStack)
		if err != nil {
			log.Printf("Error loading stack %s: %s", containerStack, err)
			continue
		}

		internalName := genInternalName(containerName)
		subsystem := &SubSystem{
			InternalName:     internalName,
			Name:             containerName,
			Stack:            stack,
			Status:           container.Status,
			ExportedPrograms: findExported(internalName, containerName),
		}

		subsystems = append(subsystems, subsystem)
	}

	return subsystems, nil
}