mirror of
https://github.com/89luca89/distrobox.git
synced 2026-08-17 16:34:42 -05:00
The legacy shell tree carried a per-file license header; the Go rewrite shipped without one. Add it to all files and enforce it going forward via golangci-lint's goheader linter, so the notice can't be dropped. Signed-off-by: Luca Di Maio <luca.dimaio1@gmail.com>
291 lines
9.2 KiB
Go
291 lines
9.2 KiB
Go
// SPDX-License-Identifier: GPL-3.0-only
|
|
//
|
|
// This file is part of the distrobox project:
|
|
// https://github.com/89luca89/distrobox
|
|
//
|
|
// Copyright (C) 2021 distrobox contributors
|
|
//
|
|
// distrobox is free software; you can redistribute it and/or modify it
|
|
// under the terms of the GNU General Public License version 3
|
|
// as published by the Free Software Foundation.
|
|
//
|
|
// distrobox is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with distrobox; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"maps"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"gopkg.in/ini.v1"
|
|
)
|
|
|
|
type Values struct {
|
|
ContainerManagerType string
|
|
SudoProgram string
|
|
Verbose bool
|
|
DefaultContainerImage string
|
|
DefaultContainerName string
|
|
|
|
// Env-set values; empty when the corresponding DBX_* var is not exported.
|
|
// Consumers prefer these over the Default* counterparts when non-empty,
|
|
// matching the shell's `[ -n "$DBX_X" ] && var=$DBX_X` pattern.
|
|
ContainerImage string
|
|
ContainerName string
|
|
ContainerHostname string
|
|
ContainerCustomHome string
|
|
ContainerHomePrefix string
|
|
ContainerAlwaysPull bool
|
|
NonInteractive bool
|
|
GenerateEntry bool
|
|
CleanPath bool
|
|
SkipWorkDir bool
|
|
UsernsNoLimit bool
|
|
RmCustomHome bool
|
|
|
|
// ScriptsDir is the directory path where the helper scripts
|
|
// (distrobox-init, distrobox-export, distrobox-host-exec) are stored.
|
|
// Resolved from DBX_SCRIPTS_DIR env var first, then the directory of the
|
|
// running binary, then ~/.local/bin, falling back to /usr/bin.
|
|
ScriptsDir string
|
|
}
|
|
|
|
func defaultsMap() map[string]string {
|
|
return map[string]string{
|
|
"container_manager": "autodetect",
|
|
"sudo_program": "sudo",
|
|
"verbose": "false",
|
|
// container_image Fedora toolbox is a sensitive default
|
|
"container_image": "registry.fedoraproject.org/fedora-toolbox:latest",
|
|
"container_name": "my-distrobox",
|
|
"container_always_pull": "false",
|
|
"non_interactive": "false",
|
|
"container_generate_entry": "true",
|
|
"container_clean_path": "false",
|
|
"container_skip_workdir": "false",
|
|
"userns_nolimit": "false",
|
|
"rm_home": "false",
|
|
}
|
|
}
|
|
|
|
func DefaultValues() *Values {
|
|
return toStruct(defaultsMap())
|
|
}
|
|
|
|
func LoadValues() (*Values, error) {
|
|
files, err := getConfigFilePaths()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get config file paths: %w", err)
|
|
}
|
|
|
|
configMaps := []map[string]string{defaultsMap()}
|
|
|
|
for _, file := range files {
|
|
config, err := readConfigFile(file)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read config file %q: %w", file, err)
|
|
}
|
|
configMaps = append(configMaps, config)
|
|
}
|
|
|
|
configMaps = append(configMaps, readEnv())
|
|
|
|
merged := mergeConfigMaps(configMaps...)
|
|
return toStruct(merged), nil
|
|
}
|
|
|
|
func toStruct(configMap map[string]string) *Values {
|
|
return &Values{
|
|
ContainerManagerType: configMap["container_manager"],
|
|
SudoProgram: configMap["sudo_program"],
|
|
Verbose: toBool(configMap["verbose"]),
|
|
DefaultContainerImage: configMap["container_image"],
|
|
DefaultContainerName: configMap["container_name"],
|
|
ContainerImage: configMap["container_image_env"],
|
|
ContainerName: configMap["container_name_env"],
|
|
ContainerHostname: configMap["container_hostname"],
|
|
ContainerCustomHome: configMap["container_user_custom_home"],
|
|
ContainerHomePrefix: configMap["container_home_prefix"],
|
|
ContainerAlwaysPull: toBool(configMap["container_always_pull"]),
|
|
NonInteractive: toBool(configMap["non_interactive"]),
|
|
GenerateEntry: toBool(configMap["container_generate_entry"]),
|
|
CleanPath: toBool(configMap["container_clean_path"]),
|
|
SkipWorkDir: toBool(configMap["container_skip_workdir"]),
|
|
UsernsNoLimit: toBool(configMap["userns_nolimit"]),
|
|
RmCustomHome: toBool(configMap["rm_home"]),
|
|
ScriptsDir: resolveScriptsDir(),
|
|
}
|
|
}
|
|
|
|
// toBool recognizes the values the shell distrobox treats as truthy
|
|
// (`1`, `true`, `yes`, `on`, case-insensitive). Anything else, including the
|
|
// empty string, is false. The previous narrower `value == "true"` silently
|
|
// ignored `DBX_VERBOSE=1` and similar — this fixes it.
|
|
func toBool(value string) bool {
|
|
switch strings.ToLower(strings.TrimSpace(value)) {
|
|
case "true", "1", "yes", "on":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// getConfigFilePaths returns a list of configuration file paths in order of priority.
|
|
func getConfigFilePaths() ([]string, error) {
|
|
execPath, err := os.Executable()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get executable path: %w", err)
|
|
}
|
|
|
|
execPath, err = filepath.EvalSymlinks(execPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to evaluate symlinks for executable path: %w", err)
|
|
}
|
|
|
|
selfDir := filepath.Dir(execPath)
|
|
|
|
xdgConfigHome := os.Getenv("XDG_CONFIG_HOME")
|
|
if xdgConfigHome == "" {
|
|
xdgConfigHome = filepath.Join(os.Getenv("HOME"), ".config")
|
|
}
|
|
|
|
home := os.Getenv("HOME")
|
|
|
|
// Source configuration files, this is done in an hierarchy so local files have
|
|
// priority over system defaults
|
|
// leave priority to environment variables.
|
|
//
|
|
// On NixOS, for the distrobox derivation to pick up a static config file shipped
|
|
// by the package maintainer the path must be relative to the script itself.
|
|
return []string{
|
|
filepath.Join(selfDir, "..", "share", "distrobox", "distrobox.conf"), // for NixOS
|
|
"/usr/share/distrobox/distrobox.conf",
|
|
"/usr/share/defaults/distrobox/distrobox.conf",
|
|
"/usr/etc/distrobox/distrobox.conf",
|
|
"/usr/local/share/distrobox/distrobox.conf",
|
|
"/etc/distrobox/distrobox.conf",
|
|
filepath.Join(xdgConfigHome, "distrobox", "distrobox.conf"),
|
|
filepath.Join(home, ".distroboxrc"),
|
|
}, nil
|
|
}
|
|
|
|
func mergeConfigMaps(configMaps ...map[string]string) map[string]string {
|
|
merged := make(map[string]string)
|
|
for _, m := range configMaps {
|
|
maps.Copy(merged, m)
|
|
}
|
|
return merged
|
|
}
|
|
|
|
func readConfigFile(filePath string) (map[string]string, error) {
|
|
cfg, err := ini.Load(filePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return make(map[string]string), nil
|
|
}
|
|
return nil, fmt.Errorf("failed to load config file %q: %w", filePath, err)
|
|
}
|
|
|
|
config := make(map[string]string)
|
|
for _, key := range cfg.Section("").Keys() {
|
|
config[key.Name()] = key.String()
|
|
}
|
|
|
|
// The reference shell uses the config key `distrobox_sudo_program`; map it
|
|
// onto our `sudo_program` slot so .conf/.distroboxrc files keep working.
|
|
// Note: .distroboxrc is parsed as INI here, not sourced as shell.
|
|
if v, ok := config["distrobox_sudo_program"]; ok {
|
|
if _, has := config["sudo_program"]; !has {
|
|
config["sudo_program"] = v
|
|
}
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
func readEnv() map[string]string {
|
|
envConfig := make(map[string]string)
|
|
|
|
if value, exists := os.LookupEnv("DBX_CONTAINER_MANAGER"); exists {
|
|
envConfig["container_manager"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_SUDO_COMMAND"); exists {
|
|
envConfig["sudo_program"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_SUDO_PROGRAM"); exists {
|
|
envConfig["sudo_program"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_VERBOSE"); exists {
|
|
envConfig["verbose"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_CONTAINER_IMAGE"); exists {
|
|
envConfig["container_image_env"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_CONTAINER_NAME"); exists {
|
|
envConfig["container_name_env"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_CONTAINER_HOSTNAME"); exists {
|
|
envConfig["container_hostname"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_CONTAINER_CUSTOM_HOME"); exists {
|
|
envConfig["container_user_custom_home"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_CONTAINER_HOME_PREFIX"); exists {
|
|
envConfig["container_home_prefix"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_CONTAINER_ALWAYS_PULL"); exists {
|
|
envConfig["container_always_pull"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_NON_INTERACTIVE"); exists {
|
|
envConfig["non_interactive"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_CONTAINER_GENERATE_ENTRY"); exists {
|
|
envConfig["container_generate_entry"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_CONTAINER_CLEAN_PATH"); exists {
|
|
envConfig["container_clean_path"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_SKIP_WORKDIR"); exists {
|
|
envConfig["container_skip_workdir"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_USERNS_NOLIMIT"); exists {
|
|
envConfig["userns_nolimit"] = value
|
|
}
|
|
if value, exists := os.LookupEnv("DBX_CONTAINER_RM_CUSTOM_HOME"); exists {
|
|
envConfig["rm_home"] = value
|
|
}
|
|
|
|
return envConfig
|
|
}
|
|
|
|
// resolveScriptsDir returns the directory path where the helper scripts should
|
|
// be stored.
|
|
func resolveScriptsDir() string {
|
|
// First check DBX_SCRIPTS_DIR env var
|
|
if dir := os.Getenv("DBX_SCRIPTS_DIR"); dir != "" {
|
|
return dir
|
|
}
|
|
|
|
// then check the path where main distrobox is installed
|
|
if exe, err := os.Executable(); err == nil {
|
|
return filepath.Dir(exe)
|
|
}
|
|
|
|
// Then, check host's HOME env var
|
|
if home := os.Getenv("HOME"); home != "" {
|
|
return filepath.Join(home, ".local", "bin")
|
|
}
|
|
|
|
// Fallback to default path
|
|
return "/usr/bin"
|
|
}
|