From a5a709026a556be4e70db5719149e21e61a31757 Mon Sep 17 00:00:00 2001 From: NopeCharon <115794254+NopeCharon@users.noreply.github.com> Date: Fri, 30 Oct 2026 03:53:44 +0530 Subject: [PATCH] fix(config): respect $HOME when resolving the default storage directory config.NewRoot resolved the default storage and log directories through os/user.Current. With cgo enabled (as in the release builds) that queries the user database via getpwuid_r and ignores the $HOME environment variable, so overriding $HOME, for example in tests or sandboxed environments, still created ~/.modctl under the system home directory. This was observed on aarch64-darwin, where every command, including `modctl version`, touched the real home directory. Resolve the home directory with os.UserHomeDir instead, which honors $HOME (%USERPROFILE% on Windows), and fall back to os/user.Current only when the environment variable is unset so that environments without $HOME keep working as before. Add unit tests for both the $HOME override and the fallback path. Fixes #580 Signed-off-by: NopeCharon <115794254+NopeCharon@users.noreply.github.com> --- pkg/config/root.go | 25 +++++++++++++++++--- pkg/config/root_test.go | 51 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 pkg/config/root_test.go diff --git a/pkg/config/root.go b/pkg/config/root.go index c9df5430..de270312 100644 --- a/pkg/config/root.go +++ b/pkg/config/root.go @@ -17,6 +17,7 @@ package config import ( + "os" "os/user" "path/filepath" ) @@ -31,17 +32,35 @@ type Root struct { } func NewRoot() (*Root, error) { - user, err := user.Current() + homeDir, err := userHomeDir() if err != nil { return nil, err } return &Root{ - StorageDir: filepath.Join(user.HomeDir, ".modctl"), + StorageDir: filepath.Join(homeDir, ".modctl"), Pprof: false, PprofAddr: "localhost:6060", DisableProgress: false, - LogDir: filepath.Join(user.HomeDir, ".modctl/logs"), + LogDir: filepath.Join(homeDir, ".modctl/logs"), LogLevel: "info", }, nil } + +// userHomeDir returns the home directory of the current user. It prefers +// os.UserHomeDir, which honors $HOME (%USERPROFILE% on Windows), so that an +// overridden home directory is respected. os/user.Current queries the user +// database, which may ignore $HOME, so it is only used as a fallback when the +// environment variable is not set. +func userHomeDir() (string, error) { + if dir, err := os.UserHomeDir(); err == nil { + return dir, nil + } + + u, err := user.Current() + if err != nil { + return "", err + } + + return u.HomeDir, nil +} diff --git a/pkg/config/root_test.go b/pkg/config/root_test.go new file mode 100644 index 00000000..e4a4c049 --- /dev/null +++ b/pkg/config/root_test.go @@ -0,0 +1,51 @@ +/* + * Copyright 2024 The ModelPack Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package config + +import ( + "os/user" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewRootRespectsHomeEnv(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + // os.UserHomeDir reads USERPROFILE on Windows. + t.Setenv("USERPROFILE", home) + + root, err := NewRoot() + require.NoError(t, err) + assert.Equal(t, filepath.Join(home, ".modctl"), root.StorageDir) + assert.Equal(t, filepath.Join(home, ".modctl", "logs"), root.LogDir) +} + +func TestNewRootFallsBackToCurrentUser(t *testing.T) { + u, err := user.Current() + require.NoError(t, err) + + t.Setenv("HOME", "") + t.Setenv("USERPROFILE", "") + + root, err := NewRoot() + require.NoError(t, err) + assert.Equal(t, filepath.Join(u.HomeDir, ".modctl"), root.StorageDir) + assert.Equal(t, filepath.Join(u.HomeDir, ".modctl", "logs"), root.LogDir) +}