package networks
import (
_ "embed"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"sync"
"github.com/goccy/go-yaml"
"github.com/sirupsen/logrus"
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
"github.com/lima-vm/lima/v2/pkg/textutil"
)
var defaultConfigTemplate string
type defaultConfigTemplateArgs struct {
SocketVMNet string
}
func defaultConfigBytes() ([]byte, error) {
var args defaultConfigTemplateArgs
candidates := []string{
"/opt/socket_vmnet/bin/socket_vmnet",
"socket_vmnet",
"/usr/local/opt/socket_vmnet/bin/socket_vmnet",
"/opt/homebrew/opt/socket_vmnet/bin/socket_vmnet",
}
for _, candidate := range candidates {
if p, err := exec.LookPath(candidate); err == nil {
realP, evalErr := filepath.EvalSymlinks(p)
if evalErr != nil {
return nil, evalErr
}
args.SocketVMNet = realP
break
} else if errors.Is(err, exec.ErrNotFound) || errors.Is(err, os.ErrNotExist) {
logrus.WithError(err).Debugf("Failed to look up socket_vmnet path %q", candidate)
} else {
logrus.WithError(err).Warnf("Failed to look up socket_vmnet path %q", candidate)
}
}
if args.SocketVMNet == "" {
args.SocketVMNet = candidates[0]
}
return textutil.ExecuteTemplate(defaultConfigTemplate, args)
}
func fillDefaults(cfg Config) (Config, error) {
usernetFound := false
if cfg.Networks == nil {
cfg.Networks = make(map[string]Network)
}
for nw := range cfg.Networks {
if cfg.Networks[nw].Mode == ModeUserV2 && cfg.Networks[nw].Gateway != nil {
usernetFound = true
}
}
if !usernetFound {
defaultCfg, err := DefaultConfig()
if err != nil {
return cfg, err
}
cfg.Networks[ModeUserV2] = defaultCfg.Networks[ModeUserV2]
}
return cfg, nil
}
func DefaultConfig() (Config, error) {
var cfg Config
b, err := defaultConfigBytes()
if err != nil {
return cfg, err
}
err = yaml.UnmarshalWithOptions(b, &cfg, yaml.Strict())
if err != nil {
return cfg, err
}
return cfg, nil
}
var cache struct {
sync.Once
cfg Config
err error
}
func ConfigFile() (string, error) {
cfgDir, err := dirnames.LimaConfigDir()
if err != nil {
return "", err
}
return filepath.Join(cfgDir, filenames.NetworksConfig), nil
}
func loadCache() {
cache.Do(func() {
var cfgFile string
cfgFile, cache.err = ConfigFile()
if cache.err != nil {
return
}
_, cache.err = os.Stat(cfgFile)
if cache.err != nil {
if !errors.Is(cache.err, os.ErrNotExist) {
return
}
cfgDir := filepath.Dir(cfgFile)
cache.err = os.MkdirAll(cfgDir, 0o755)
if cache.err != nil {
cache.err = fmt.Errorf("could not create %q directory: %w", cfgDir, cache.err)
return
}
var b []byte
b, cache.err = defaultConfigBytes()
if cache.err != nil {
return
}
cache.err = os.WriteFile(cfgFile, b, 0o644)
if cache.err != nil {
return
}
}
var b []byte
b, cache.err = os.ReadFile(cfgFile)
if cache.err != nil {
return
}
cache.err = yaml.Unmarshal(b, &cache.cfg)
if cache.err != nil {
cache.err = fmt.Errorf("cannot parse %q: %w", cfgFile, cache.err)
return
}
var strictCfg Config
if strictErr := yaml.UnmarshalWithOptions(b, &strictCfg, yaml.Strict()); strictErr != nil {
logrus.WithError(strictErr).Warn("Non-strict YAML is deprecated and will be unsupported in a future version of Lima: " + cfgFile)
}
cache.cfg, cache.err = fillDefaults(cache.cfg)
if cache.err != nil {
cache.err = fmt.Errorf("cannot fill default %q: %w", cfgFile, cache.err)
}
})
}
func LoadConfig() (Config, error) {
loadCache()
return cache.cfg, cache.err
}
func Sock(name string) (string, error) {
loadCache()
if cache.err != nil {
return "", cache.err
}
if err := cache.cfg.Check(name); err != nil {
return "", err
}
if cache.cfg.Paths.SocketVMNet == "" {
return "", errors.New("socketVMNet is not set")
}
return cache.cfg.Sock(name), nil
}
func IsUsernet(name string) bool {
loadCache()
if cache.err != nil {
return false
}
isUsernet, err := cache.cfg.Usernet(name)
if err != nil {
return false
}
return isUsernet
}