Apx struct

Fields:

  • Cnf (*settings.Config)

Methods:

EssentialChecks


Returns:
  • error

Show/Hide Method Body
{
	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
}

CheckContainerTools


Returns:
  • error

Show/Hide Method Body
{
	_, err := os.Stat(a.Cnf.DistroboxPath)
	if err != nil {
		if os.IsNotExist(err) {
			return errors.New("distrobox is not installed")
		}
		return err
	}

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

	return nil
}

CheckAndCreateUserStacksDirectory


Returns:
  • error

Show/Hide Method Body
{
	_, 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
}

CheckAndCreateApxStorageDirectory


Returns:
  • error

Show/Hide Method Body
{
	_, 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
}

CheckAndCreateApxUserPkgManagersDirectory


Returns:
  • error

Show/Hide Method Body
{
	_, 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
}

NewApx function

Parameters:

  • cnf *settings.Config

Returns:

  • *Apx
Show/Hide Function Body
{
	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
}

NewStandardApx function

Returns:

  • *Apx
Show/Hide Function Body
{
	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
}

dbox struct

Fields:

  • Engine (string)
  • EngineBinary (string)
  • Version (string)

Methods:

RunCommand


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

Returns:
  • []byte
  • error

Show/Hide Method Body
{
	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}
	}

	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

	cmd.Env = os.Environ()
	cmd.Env = append(cmd.Env, "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" {
		cmd.Env = append(cmd.Env, "CONTAINER_STORAGE_DRIVER="+apx.Cnf.StorageDriver)
		// cmd.Env = append(cmd.Env, "XDG_DATA_HOME="+apx.Cnf.ApxStoragePath)
	} else if d.Engine == "docker" {
		cmd.Env = append(cmd.Env, "DOCKER_STORAGE_DRIVER="+apx.Cnf.StorageDriver)
		// cmd.Env = append(cmd.Env, "DOCKER_DATA_ROOT="+apx.Cnf.ApxStoragePath)
	}

	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("Runing 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
	}

	cmd.Stdout = ioutil.Discard

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

ListContainers


Parameters:
  • rootFull bool

Returns:
  • []dboxContainer
  • error

Show/Hide Method Body
{
	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


Parameters:
  • name string
  • rootFull bool

Returns:
  • *dboxContainer
  • error

Show/Hide Method Body
{
	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")
}

ContainerDelete


Parameters:
  • name string
  • rootFull bool

Returns:
  • error

Show/Hide Method Body
{
	_, err := d.RunCommand("rm", []string{
		"--force",
		name,
	}, []string{}, false, false, true, rootFull, false)
	return err
}

CreateContainer


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

Show/Hide Method Body
{
	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
}

RunContainerCommand


Parameters:
  • name string
  • command []string
  • rootFull bool
  • detachedMode bool

Returns:
  • error

Show/Hide Method Body
{
	args := []string{
		"--name", name,
		"--",
	}

	args = append(args, command...)

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

ContainerExec


Parameters:
  • name string
  • captureOutput bool
  • muteOutput bool
  • rootFull bool
  • detachedMode bool
  • args ...string

Returns:
  • string
  • error

Show/Hide Method Body
{
	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
}

ContainerEnter


Parameters:
  • name string
  • rootFull bool

Returns:
  • error

Show/Hide Method Body
{
	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
}

ContainerStart


Parameters:
  • name string
  • rootFull bool

Returns:
  • error

Show/Hide Method Body
{
	_, err := d.RunCommand("start", []string{
		name,
	}, []string{}, true, false, false, rootFull, false)
	return err
}

ContainerStop


Parameters:
  • name string
  • rootFull bool

Returns:
  • error

Show/Hide Method Body
{
	finalArgs := []string{
		name,
		"--yes",
	}

	engineFlags := []string{}

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

ContainerExport


Parameters:
  • name string
  • delete bool
  • rootFull bool
  • args ...string

Returns:
  • error

Show/Hide Method Body
{
	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
}

ContainerExportDesktopEntry


Parameters:
  • containerName string
  • appName string
  • label string
  • rootFull bool

Returns:
  • error

Show/Hide Method Body
{
	args := []string{"--app", appName, "--export-label", label}
	return d.ContainerExport(containerName, false, rootFull, args...)
}

ContainerUnexportDesktopEntry


Parameters:
  • containerName string
  • appName string
  • rootFull bool

Returns:
  • error

Show/Hide Method Body
{
	args := []string{"--app", appName}
	return d.ContainerExport(containerName, true, rootFull, args...)
}

ContainerExportBin


Parameters:
  • containerName string
  • binary string
  • exportPath string
  • rootFull bool

Returns:
  • error

Show/Hide Method Body
{
	args := []string{"--bin", binary, "--export-path", exportPath}
	return d.ContainerExport(containerName, false, rootFull, args...)
}

ContainerUnexportBin


Parameters:
  • containerName string
  • binary string
  • rootFull bool

Returns:
  • error

Show/Hide Method Body
{
	args := []string{"--bin", binary}
	return d.ContainerExport(containerName, true, rootFull, args...)
}

dboxContainer struct

Fields:

  • ID (string)
  • CreatedAt (string)
  • Status (string)
  • Labels (map[string]string)
  • Name (string)

NewDbox function

Returns:

  • *dbox
  • error
Show/Hide Function Body
{
	engineBinary, engine := getEngine()

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

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

getEngine function

Returns:

  • string
  • string
Show/Hide Function Body
{
	podmanBinary, err := exec.LookPath("podman")
	if err == nil {
		return podmanBinary, "podman"
	}

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

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

dboxGetVersion function

Returns:

  • version string
  • err error
Show/Hide Function Body
{
	output, err := exec.Command(apx.Cnf.DistroboxPath, "version").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
}

IsOverlayTypeFS function

Returns:

  • bool
Show/Hide Function Body
{
	out, err := exec.Command("df", "-T", "/").Output()
	if err != nil {
		return false
	}

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

ExitIfOverlayTypeFS function

Show/Hide Function Body
{
	if IsOverlayTypeFS() {
		log.Default().Printf("Apx does not work with overlay type filesystem.")
		os.Exit(1)
	}
}

hasNvidiaGPU function

Returns:

  • bool
Show/Hide Function Body
{
	_, err := os.Stat("/dev/nvidia0")
	return err == nil
}

PkgManager struct

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

Fields:

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

Methods:

Save

Save saves the package manager to the specified path.


Returns:
  • error

Show/Hide Method Body
{
	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

Remove removes the package manager from the specified path.


Returns:
  • error

Show/Hide Method Body
{
	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

GenCmd generates the command to run inside the container.


Parameters:
  • cmd string
  • args ...string

Returns:
  • []string

Show/Hide Method Body
{
	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

Export exports the package manager to the specified path.


Parameters:
  • path string

Returns:
  • error

Show/Hide Method Body
{
	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
}

NewPkgManager function

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:

  • *PkgManager
Show/Hide Function Body
{
	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,
	}
}

LoadPkgManager function

LoadPkgManager loads a package manager from the specified path.

Parameters:

  • name string

Returns:

  • *PkgManager
  • error
Show/Hide Function Body
{
	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
}

loadPkgManagerFromPath function

loadPkgManagerFromPath loads a package manager from the specified path.

Parameters:

  • path string

Returns:

  • *PkgManager
  • error
Show/Hide Function Body
{
	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
}

ListPkgManagers function

ListPkgManagers lists all the package managers.

Returns:

  • []*PkgManager
Show/Hide Function Body
{
	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
}

listPkgManagersFromPath function

listPkgManagersFromPath lists all the package managers from the specified path.

Parameters:

  • path string

Returns:

  • []*PkgManager
Show/Hide Function Body
{
	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
}

PkgManagerExists function

PkgManagerExists checks if the package manager exists.

Parameters:

  • name string

Returns:

  • bool
Show/Hide Function Body
{
	_, err := LoadPkgManager(name)
	return err == nil
}

LoadPkgManagerFromPath function

LoadPkgManagerFromPath loads a package manager from the specified path.

Parameters:

  • path string

Returns:

  • *PkgManager
  • error
Show/Hide Function Body
{
	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
}

Stack struct

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

Fields:

  • Name (string)
  • Base (string)
  • Packages ([]string)
  • PkgManager (string)
  • BuiltIn (bool)

Methods:

Save

Save saves the stack to a YAML file.


Returns:
  • error

Show/Hide Method Body
{
	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

GetPkgManager returns the package manager of the stack.


Returns:
  • *PkgManager
  • error

Show/Hide Method Body
{
	pkgManager, err := LoadPkgManager(stack.PkgManager)
	if err != nil {
		return nil, err
	}

	return pkgManager, nil
}

Remove

Remove removes the stack file.


Returns:
  • error

Show/Hide Method Body
{
	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

Export exports the stack YAML to the specified path.


Parameters:
  • path string

Returns:
  • error

Show/Hide Method Body
{
	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
}

NewStack function

NewStack creates a new Stack instance.

Parameters:

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

Returns:

  • *Stack
Show/Hide Function Body
{
	return &Stack{
		Name:       name,
		Base:       base,
		Packages:   packages,
		PkgManager: pkgManager,
		BuiltIn:    builtIn,
	}
}

LoadStack function

LoadStack loads a stack from the specified path.

Parameters:

  • name string

Returns:

  • *Stack
  • error
Show/Hide Function Body
{
	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
}

LoadStackFromPath function

LoadStackFromPath loads a stack from the specified path.

Parameters:

  • path string

Returns:

  • *Stack
  • error
Show/Hide Function Body
{
	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
}

ListStacks function

ListStacks returns a list of all stacks.

Returns:

  • []*Stack
Show/Hide Function Body
{
	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
}

ListStackForPkgManager function

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

Parameters:

  • pkgManager string

Returns:

  • []*Stack
Show/Hide Function Body
{
	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
}

listStacksFromPath function

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:

  • []*Stack
Show/Hide Function Body
{
	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
}

StackExists function

StackExists checks if a stack exists.

Parameters:

  • name string

Returns:

  • bool
Show/Hide Function Body
{
	s, _ := LoadStack(name)
	return s != nil
}

SubSystem struct

Fields:

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

Methods:

Create


Returns:
  • error

Show/Hide Method Body
{
	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


Parameters:
  • captureOutput bool
  • detachedMode bool
  • args ...string

Returns:
  • string
  • error

Show/Hide Method Body
{
	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


Returns:
  • error

Show/Hide Method Body
{
	dbox, err := NewDbox()
	if err != nil {
		return err
	}
	return dbox.ContainerEnter(s.InternalName, s.IsRootfull)
}

Start


Returns:
  • error

Show/Hide Method Body
{
	dbox, err := NewDbox()
	if err != nil {
		return err
	}
	return dbox.ContainerStart(s.InternalName, s.IsRootfull)
}

Stop


Returns:
  • error

Show/Hide Method Body
{
	dbox, err := NewDbox()
	if err != nil {
		return err
	}
	return dbox.ContainerStop(s.InternalName, s.IsRootfull)
}

Remove


Returns:
  • error

Show/Hide Method Body
{
	dbox, err := NewDbox()
	if err != nil {
		return err
	}

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

Reset


Returns:
  • error

Show/Hide Method Body
{
	err := s.Remove()
	if err != nil {
		return err
	}

	return s.Create()
}

ExportDesktopEntry


Parameters:
  • appName string

Returns:
  • error

Show/Hide Method Body
{
	dbox, err := NewDbox()
	if err != nil {
		return err
	}

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

ExportDesktopEntries


Parameters:
  • args ...string

Returns:
  • int
  • error

Show/Hide Method Body
{
	exportedN := 0

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

		exportedN++
	}

	return exportedN, nil
}

UnexportDesktopEntries


Parameters:
  • args ...string

Returns:
  • int
  • error

Show/Hide Method Body
{
	exportedN := 0

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

		exportedN++
	}

	return exportedN, nil
}

ExportBin


Parameters:
  • binary string
  • exportPath string

Returns:
  • error

Show/Hide Method Body
{
	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
}

UnexportDesktopEntry


Parameters:
  • appName string

Returns:
  • error

Show/Hide Method Body
{
	dbox, err := NewDbox()
	if err != nil {
		return err
	}

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

UnexportBin


Parameters:
  • binary string
  • exportPath string

Returns:
  • error

Show/Hide Method Body
{
	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)
}

NewSubSystem function

Parameters:

  • name string
  • stack *Stack
  • home string
  • hasInit bool
  • isManaged bool
  • isRootfull bool
  • isUnshared bool
  • hasNvidiaIntegration bool
  • hostname string

Returns:

  • *SubSystem
  • error
Show/Hide Function Body
{
	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
}

genInternalName function

Parameters:

  • name string

Returns:

  • string
Show/Hide Function Body
{
	return fmt.Sprintf("apx-%s", strings.ReplaceAll(strings.ToLower(name), " ", "-"))
}

findExportedBinaries function

Parameters:

  • internalName string

Returns:

  • map[string]map[string]string
Show/Hide Function Body
{
	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()

		scanner := bufio.NewScanner(file)
		const maxTokenSize = 1024 * 1024
		buf := make([]byte, maxTokenSize)
		scanner.Buffer(buf, maxTokenSize)
		for scanner.Scan() {
			if scanner.Text() == "# distrobox_binary" {
				scanner.Scan()
				if strings.HasSuffix(scanner.Text(), internalName) {
					name := filepath.Base(path)
					binaries[name] = map[string]string{
						"Exec": path,
						// "Icon":        pIcon,
						"Name": name,
						// "GenericName": pGenericName,
					}
				}
				break
			}
		}

		if err := scanner.Err(); err != nil {
			return err
		}

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

	return binaries
}

findExportedPrograms function

Parameters:

  • internalName string
  • name string

Returns:

  • map[string]map[string]string
Show/Hide Function Body
{
	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
}

findExported function

Parameters:

  • internalName string
  • name string

Returns:

  • map[string]map[string]string
Show/Hide Function Body
{
	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
}

LoadSubSystem function

Parameters:

  • name string
  • isRootFull bool

Returns:

  • *SubSystem
  • error
Show/Hide Function Body
{
	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
}

ListSubSystems function

Parameters:

  • includeManaged bool
  • includeRootFull bool

Returns:

  • []*SubSystem
  • error
Show/Hide Function Body
{
	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
}

ListSubsystemForStack function

ListSubsystemForStack returns a list of subsystems for the specified stack.

Parameters:

  • stackName string

Returns:

  • []*SubSystem
  • error
Show/Hide Function Body
{
	dbox, err := NewDbox()
	if err != nil {
		return nil, err
	}

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

	subsystems := []*SubSystem{}
	for _, container := range containers {
		if _, ok := container.Labels["name"]; !ok {
			continue
		}

		stack, err := LoadStack(stackName)
		if err != nil {
			log.Printf("Error loading stack %s: %s", stackName, 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"]),
		}

		if subsystem.Stack.Name == stack.Name {
			subsystems = append(subsystems, subsystem)
		}
	}

	return subsystems, nil
}

RootCheck function

Parameters:

  • display bool

Returns:

  • bool
Show/Hide Function Body
{
	if os.Geteuid() != 0 {
		if display {
			fmt.Println("You must be root to run this command")
		}
		return false
	}
	return true
}

AskConfirmation function

Parameters:

  • s string

Returns:

  • bool
Show/Hide Function Body
{
	var response string
	fmt.Print(s + " [y/N]: ")
	fmt.Scanln(&response)
	if response == "y" || response == "Y" {
		return true
	}
	return false
}

CopyToUserTemp function

Parameters:

  • path string

Returns:

  • string
  • error
Show/Hide Function Body
{
	user, err := user.Current()
	if err != nil {
		return "", err
	}

	cacheDir := filepath.Join(user.HomeDir, ".cache", "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
}

getPrettifiedDate function

getPrettifiedDate returns a human readable date from a timestamp

Parameters:

  • date string

Returns:

  • string
Show/Hide Function Body
{
	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")
}

CreateApxTable function

Parameters:

  • writer io.Writer

Returns:

  • *tablewriter.Table
Show/Hide Function Body
{
	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
}

CopyFile function

Parameters:

  • src string
  • dst string

Returns:

  • error
Show/Hide Function Body
{
	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
}

SelectYamlFile function

Parameters:

  • basePath string
  • name string

Returns:

  • string
Show/Hide Function Body
{
	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
}

fmt import

Import example:

import "fmt"

github.com/vanilla-os/apx/v2/settings import

Import example:

import "github.com/vanilla-os/apx/v2/settings"

errors import

Import example:

import "errors"

fmt import

Import example:

import "fmt"

log import

Import example:

import "log"

os import

Import example:

import "os"

os/exec import

Import example:

import "os/exec"

strings import

Import example:

import "strings"

syscall import

Import example:

import "syscall"

io/ioutil import

Import example:

import "io/ioutil"

errors import

Import example:

import "errors"

fmt import

Import example:

import "fmt"

log import

Import example:

import "log"

os import

Import example:

import "os"

os/exec import

Import example:

import "os/exec"

strings import

Import example:

import "strings"

errors import

Import example:

import "errors"

fmt import

Import example:

import "fmt"

io import

Import example:

import "io"

os import

Import example:

import "os"

path/filepath import

Import example:

import "path/filepath"

strings import

Import example:

import "strings"

gopkg.in/yaml.v2 import

Import example:

import "gopkg.in/yaml.v2"

errors import

Import example:

import "errors"

io import

Import example:

import "io"

os import

Import example:

import "os"

path/filepath import

Import example:

import "path/filepath"

gopkg.in/yaml.v2 import

Import example:

import "gopkg.in/yaml.v2"

bufio import

Import example:

import "bufio"

fmt import

Import example:

import "fmt"

io import

Import example:

import "io"

io/fs import

Import example:

import "io/fs"

log import

Import example:

import "log"

os import

Import example:

import "os"

path/filepath import

Import example:

import "path/filepath"

strings import

Import example:

import "strings"

github.com/google/uuid import

Import example:

import "github.com/google/uuid"

errors import

Import example:

import "errors"

fmt import

Import example:

import "fmt"

io import

Import example:

import "io"

os import

Import example:

import "os"

os/user import

Import example:

import "os/user"

path/filepath import

Import example:

import "path/filepath"

time import

Import example:

import "time"

github.com/olekukonko/tablewriter import

Import example:

import "github.com/olekukonko/tablewriter"