core
packageAPI reference for the core
package.
Imports
(15)RootCheck
Parameters
Returns
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
}
AskConfirmation
Parameters
Returns
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
}
CopyToUserTemp
Parameters
Returns
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
}
getPrettifiedDate
getPrettifiedDate returns a human readable date from a timestamp
Parameters
Returns
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")
}
CreateApxTable
Parameters
Returns
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
}
CopyFile
Parameters
Returns
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
}
SelectYamlFile
Parameters
Returns
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
}
Apx
type Apx struct
Methods
Returns
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
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
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
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
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 |
NewApx
Parameters
Returns
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
}
NewStandardApx
Returns
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
}
dbox
type dbox struct
Methods
Parameters
Returns
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
Returns
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
}
Parameters
Returns
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
Returns
func (*dbox) ContainerDelete(name string, rootFull bool) error
{
_, err := d.RunCommand("rm", []string{
"--force",
name,
}, []string{}, false, false, true, rootFull, false)
return err
}
Parameters
Returns
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
Returns
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
}
Parameters
Returns
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
Returns
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
Returns
func (*dbox) ContainerStart(name string, rootFull bool) error
{
_, err := d.RunCommand("start", []string{
name,
}, []string{}, true, false, false, rootFull, false)
return err
}
Parameters
Returns
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
Returns
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
Returns
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
Returns
func (*dbox) ContainerUnexportDesktopEntry(containerName string, appName string, rootFull bool) error
{
args := []string{"--app", appName}
return d.ContainerExport(containerName, true, rootFull, args...)
}
Parameters
Returns
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
Returns
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 |
dboxContainer
type dboxContainer struct
Fields
| Name | Type | Description |
|---|---|---|
| ID | string | |
| CreatedAt | string | |
| Status | string | |
| Labels | map[string]string | |
| Name | string |
NewDbox
Returns
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
}
getEngine
Returns
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 "", ""
}
dboxGetVersion
Returns
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
}
IsOverlayTypeFS
Returns
func IsOverlayTypeFS() bool
{
out, err := exec.Command("df", "-T", "/").Output()
if err != nil {
return false
}
return strings.Contains(string(out), "overlay")
}
ExitIfOverlayTypeFS
func ExitIfOverlayTypeFS()
{
if IsOverlayTypeFS() {
log.Default().Printf("Apx does not work with overlay type filesystem.")
os.Exit(1)
}
}
hasNvidiaGPU
Returns
func hasNvidiaGPU() bool
{
_, err := os.Stat("/dev/nvidia0")
return err == nil
}
PkgManager
PkgManager represents a package manager in Apx, a set of instructions to handle a package manager.
type PkgManager struct
Methods
Save saves the package manager to the specified path.
Returns
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 removes the package manager from the specified path.
Returns
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 generates the command to run inside the container.
Parameters
Returns
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 exports the package manager to the specified path.
Parameters
Returns
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 |
NewPkgManager
NewPkgManager creates a new PkgManager instance.
Parameters
Returns
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,
}
}
LoadPkgManager
LoadPkgManager loads a package manager from the specified path.
Parameters
Returns
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
}
loadPkgManagerFromPath
loadPkgManagerFromPath loads a package manager from the specified path.
Parameters
Returns
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
}
ListPkgManagers
ListPkgManagers lists all the package managers.
Returns
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
}
listPkgManagersFromPath
listPkgManagersFromPath lists all the package managers from the specified path.
Parameters
Returns
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
}
PkgManagerExists
PkgManagerExists checks if the package manager exists.
Parameters
Returns
func PkgManagerExists(name string) bool
{
_, err := LoadPkgManager(name)
return err == nil
}
LoadPkgManagerFromPath
LoadPkgManagerFromPath loads a package manager from the specified path.
Parameters
Returns
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
}
Stack
Stack represents a stack in Apx, a set of instructions to build a container.
type Stack struct
Methods
Save saves the stack to a YAML file.
Returns
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 returns the package manager of the stack.
Returns
func (*Stack) GetPkgManager() (*PkgManager, error)
{
pkgManager, err := LoadPkgManager(stack.PkgManager)
if err != nil {
return nil, err
}
return pkgManager, nil
}
Remove removes the stack file.
Returns
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 exports the stack YAML to the specified path.
Parameters
Returns
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 |
NewStack
NewStack creates a new Stack instance.
Parameters
Returns
func NewStack(name, base string, packages []string, pkgManager string, builtIn bool) *Stack
{
return &Stack{
Name: name,
Base: base,
Packages: packages,
PkgManager: pkgManager,
BuiltIn: builtIn,
}
}
LoadStack
LoadStack loads a stack from the specified path.
Parameters
Returns
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
}
LoadStackFromPath
LoadStackFromPath loads a stack from the specified path.
Parameters
Returns
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
}
ListStacks
ListStacks returns a list of all stacks.
Returns
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
}
ListStackForPkgManager
ListStackForPkgManager returns a list of stacks for the specified package manager.
Parameters
Returns
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
}
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
Returns
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
}
StackExists
StackExists checks if a stack exists.
Parameters
Returns
func StackExists(name string) bool
{
s, _ := LoadStack(name)
return s != nil
}
SubSystem
type SubSystem struct
Methods
Returns
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
}
Parameters
Returns
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
}
Returns
func (*SubSystem) Enter() error
{
dbox, err := NewDbox()
if err != nil {
return err
}
return dbox.ContainerEnter(s.InternalName, s.IsRootfull)
}
Returns
func (*SubSystem) Start() error
{
dbox, err := NewDbox()
if err != nil {
return err
}
return dbox.ContainerStart(s.InternalName, s.IsRootfull)
}
Returns
func (*SubSystem) Stop() error
{
dbox, err := NewDbox()
if err != nil {
return err
}
return dbox.ContainerStop(s.InternalName, s.IsRootfull)
}
Returns
func (*SubSystem) Remove() error
{
dbox, err := NewDbox()
if err != nil {
return err
}
return dbox.ContainerDelete(s.InternalName, s.IsRootfull)
}
Returns
func (*SubSystem) Reset() error
{
err := s.Remove()
if err != nil {
return err
}
return s.Create()
}
Parameters
Returns
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
Returns
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
Returns
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
}
Parameters
Returns
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
Returns
func (*SubSystem) UnexportDesktopEntry(appName string) error
{
dbox, err := NewDbox()
if err != nil {
return err
}
return dbox.ContainerUnexportDesktopEntry(s.InternalName, appName, s.IsRootfull)
}
Parameters
Returns
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 |
NewSubSystem
Parameters
Returns
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
}
genInternalName
Parameters
Returns
func genInternalName(name string) string
{
return fmt.Sprintf("apx-%s", strings.ReplaceAll(strings.ToLower(name), " ", "-"))
}
findExportedBinaries
Parameters
Returns
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
}
findExportedPrograms
Parameters
Returns
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
}
findExported
Parameters
Returns
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
}
LoadSubSystem
Parameters
Returns
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
}
ListSubSystems
Parameters
Returns
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
}
ListSubsystemForStack
ListSubsystemForStack returns a list of subsystems for the specified stack.
Parameters
Returns
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
}